]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #8681 from rgacogne/auth-stats-rings-size
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Mon, 27 Jan 2020 09:40:33 +0000 (10:40 +0100)
committerGitHub <noreply@github.com>
Mon, 27 Jan 2020 09:40:33 +0000 (10:40 +0100)
auth: Add metrics about the size of our in-memory rings

85 files changed:
builder-support/debian/recursor/debian-buster/pdns-recursor.postinst
builder-support/debian/recursor/debian-buster/rules
builder-support/debian/recursor/debian-jessie/pdns-recursor.postinst
builder-support/debian/recursor/debian-stretch/control
builder-support/debian/recursor/debian-stretch/pdns-recursor.postinst
builder-support/debian/recursor/debian-stretch/rules
builder-support/dockerfiles/Dockerfile.target.ubuntu-bionic
builder-support/specs/dnsdist.spec
builder-support/specs/pdns-recursor.spec
contrib/ProtobufLogger.py
docs/backends/remote.rst
docs/dnssec/operational.rst
docs/secpoll.zone
ext/yahttp/yahttp/reqresp.cpp
modules/gpgsqlbackend/spgsql.cc
modules/gpgsqlbackend/spgsql.hh
modules/lmdbbackend/lmdbbackend.cc
modules/lmdbbackend/lmdbbackend.hh
modules/remotebackend/remotebackend.cc
modules/remotebackend/remotebackend.hh
modules/remotebackend/test-remotebackend.cc
pdns/auth-packetcache.cc
pdns/auth-packetcache.hh
pdns/auth-querycache.cc
pdns/auth-querycache.hh
pdns/common_startup.cc
pdns/dnsdist-console.cc
pdns/dnsdist-dynblocks.hh
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua-bindings.cc
pdns/dnsdist-lua-inspection.cc
pdns/dnsdist-lua.cc
pdns/dnsdist-lua.hh
pdns/dnsdist-tcp.cc
pdns/dnsdist-web.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-dynblocks.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-healthchecks.cc [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-healthchecks.hh [new file with mode: 0644]
pdns/dnsdistdist/dnsdist-lua-bindings-protobuf.cc
pdns/dnsdistdist/docs/guides/downstreams.rst
pdns/dnsdistdist/docs/guides/serverselection.rst
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/docs/reference/protobuf.rst
pdns/dnsdistdist/docs/rules-actions.rst
pdns/dnsdistdist/libssl.cc
pdns/dnsdistdist/test-dnsdistdynblocks_hh.cc
pdns/dnsmessage.proto
pdns/dnsseckeeper.hh
pdns/dynhandler.cc
pdns/filterpo.cc
pdns/iputils.hh
pdns/lua-recursor4.cc
pdns/mplexer.hh
pdns/packethandler.cc
pdns/pdns_recursor.cc
pdns/protobuf.cc
pdns/protobuf.hh
pdns/recursordist/docs/changelog/4.3.rst
pdns/recursordist/docs/settings.rst
pdns/recursordist/test-filterpo_cc.cc
pdns/recursordist/test-syncres_cc.cc
pdns/recursordist/test-syncres_cc.hh
pdns/recursordist/test-syncres_cc3.cc
pdns/recursordist/test-syncres_cc6.cc
pdns/sdig.cc
pdns/syncres.cc
pdns/syncres.hh
pdns/tcpreceiver.cc
pdns/ueberbackend.cc
pdns/validate.cc
pdns/ws-auth.cc
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/test_Advanced.py
regression-tests.dnsdist/test_Basics.py
regression-tests.dnsdist/test_DOH.py
regression-tests.dnsdist/test_DynBlocks.py
regression-tests.dnsdist/test_EDNSSelfGenerated.py
regression-tests.dnsdist/test_RecordsCount.py
regression-tests.dnsdist/test_Routing.py
regression-tests.dnsdist/test_Spoofing.py
regression-tests.dnsdist/test_TCPKeepAlive.py
regression-tests.recursor-dnssec/test_RPZ.py

index c4a755daadc28eb56ed6e31e51fe5b252bab74bd..4e1da7099252129ed5d1b5683cd92ee006ce0983 100644 (file)
@@ -3,8 +3,12 @@ set -e
 
 case "$1" in
   configure)
-    addgroup --system pdns
-    adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+    if [ -z "`getent group pdns`" ]; then
+      addgroup --system pdns
+    fi
+    if [ -z "`getent passwd pdns`" ]; then
+      adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+    fi
     if [ "`stat -c '%U:%G' /etc/powerdns/recursor.conf`" = "root:root" ]; then
       chown root:pdns /etc/powerdns/recursor.conf
       # Make sure that pdns can read it; the default used to be 0600
index 59e60b8081bb2fe21cf643779438c9a6d112c2d2..2f0ffd17a12d7c73c95a5ea288e19c8367f89197 100755 (executable)
@@ -31,6 +31,7 @@ override_dh_auto_configure:
                --with-libcap \
                --with-libsodium \
                --with-protobuf=yes \
+               --enable-dnstap \
                --without-net-snmp \
                --disable-silent-rules \
                --with-service-user=pdns \
index c4a755daadc28eb56ed6e31e51fe5b252bab74bd..4e1da7099252129ed5d1b5683cd92ee006ce0983 100644 (file)
@@ -3,8 +3,12 @@ set -e
 
 case "$1" in
   configure)
-    addgroup --system pdns
-    adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+    if [ -z "`getent group pdns`" ]; then
+      addgroup --system pdns
+    fi
+    if [ -z "`getent passwd pdns`" ]; then
+      adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+    fi
     if [ "`stat -c '%U:%G' /etc/powerdns/recursor.conf`" = "root:root" ]; then
       chown root:pdns /etc/powerdns/recursor.conf
       # Make sure that pdns can read it; the default used to be 0600
index 0b23de49eb6972198e22bbfccda66beddbf73562..289190b678b3033dbc99970cff7c20016fcb8a96 100644 (file)
@@ -10,6 +10,7 @@ Build-Depends: debhelper (>= 10~),
                libcap-dev,
                libluajit-5.1-dev,
                libprotobuf-dev,
+               libfstrm-dev,
                libsodium-dev,
                libssl-dev,
                libsystemd-dev [linux-any],
index c4a755daadc28eb56ed6e31e51fe5b252bab74bd..4e1da7099252129ed5d1b5683cd92ee006ce0983 100644 (file)
@@ -3,8 +3,12 @@ set -e
 
 case "$1" in
   configure)
-    addgroup --system pdns
-    adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+    if [ -z "`getent group pdns`" ]; then
+      addgroup --system pdns
+    fi
+    if [ -z "`getent passwd pdns`" ]; then
+      adduser --system --home /var/spool/powerdns --shell /bin/false --ingroup pdns --disabled-password --disabled-login --gecos "PowerDNS" pdns
+    fi
     if [ "`stat -c '%U:%G' /etc/powerdns/recursor.conf`" = "root:root" ]; then
       chown root:pdns /etc/powerdns/recursor.conf
       # Make sure that pdns can read it; the default used to be 0600
index 59e60b8081bb2fe21cf643779438c9a6d112c2d2..2f0ffd17a12d7c73c95a5ea288e19c8367f89197 100755 (executable)
@@ -31,6 +31,7 @@ override_dh_auto_configure:
                --with-libcap \
                --with-libsodium \
                --with-protobuf=yes \
+               --enable-dnstap \
                --without-net-snmp \
                --disable-silent-rules \
                --with-service-user=pdns \
index e6d3c12d129eda59707b84fa6816ff550bf020a1..bf22d1dfab8eae28f31446a356e5bcac3a9dbf66 100644 (file)
@@ -9,11 +9,11 @@ RUN apt-get update && apt-get -y dist-upgrade
 @INCLUDE Dockerfile.debbuild-prepare
 
 @IF [ ! -z "$M_authoritative" ]
-ADD builder-support/debian/authoritative/debian-stretch/ pdns-${BUILDER_VERSION}/debian/
+ADD builder-support/debian/authoritative/debian-buster/ pdns-${BUILDER_VERSION}/debian/
 @ENDIF
 
 @IF [ ! -z "$M_recursor" ]
-ADD builder-support/debian/recursor/debian-stretch/ pdns-recursor-${BUILDER_VERSION}/debian/
+ADD builder-support/debian/recursor/debian-buster/ pdns-recursor-${BUILDER_VERSION}/debian/
 @ENDIF
 
 @IF [ ! -z "$M_dnsdist" ]
index 145f2d3b306e55b09031ec52c71112af1257244a..5614e978cb7981e202f93dd1ecf6b29f16af7603 100644 (file)
@@ -56,10 +56,7 @@ Requires(pre): shadow
 %endif
 %if 0%{?rhel} >= 7
 Requires(pre): shadow-utils
-%if 0%{?rhel} == 7
-# No fstrm in EPEL 8 (yet) https://bugzilla.redhat.com/show_bug.cgi?id=1760298
 BuildRequires: fstrm-devel
-%endif
 %systemd_requires
 %endif
 
@@ -101,12 +98,10 @@ sed -i '/^ExecStart/ s/dnsdist/dnsdist -u dnsdist -g dnsdist/' dnsdist.service.i
   --without-protobuf \
   --without-net-snmp
 %endif
-%if 0%{?rhel} == 7
-  --enable-dnstap \
-%endif
 %if 0%{?rhel} >= 7
   --with-gnutls \
   --with-protobuf \
+  --enable-dnstap \
   --with-lua=%{lua_implementation} \
   --with-libcap \
   --with-libsodium \
index 7f354b8590846a9da6e22863c71f7894f05eb884..39df2502128c1a5695c5203a2b90c7f312a93660 100644 (file)
@@ -35,12 +35,8 @@ BuildRequires: libatomic
 %if 0%{?rhel} >= 7
 BuildRequires: protobuf-compiler
 BuildRequires: protobuf-devel
-
-%if 0%{?rhel} == 7
-# No fstrm in EPEL 8 yet
 BuildRequires: fstrm-devel
 %endif
-%endif
 
 BuildRequires: openssl-devel
 BuildRequires: net-snmp-devel
@@ -83,6 +79,7 @@ package if you need a dns cache for your network.
 make %{?_smp_mflags} LIBRARY_PATH=/usr/lib64/boost148
 %else
     --with-protobuf \
+    --enable-dnstap \
     --with-libcap \
     --with-lua=%{lua_implementation} \
     --enable-systemd --with-systemd=%{_unitdir}
index c88653ce799beee38ce4db2b032312970f9eb955..a423c38f273fb49b1b947a205e1ff54d5a64d6ab 100644 (file)
@@ -138,6 +138,8 @@ class PDNSPBConnHandler(object):
             datestr = datestr + '.' + str(msg.timeUsec)
         ipfromstr = 'N/A'
         iptostr = 'N/A'
+        toportstr = ''
+        fromportstr = ''
         fromvalue = getattr(msg, 'from')
         if msg.socketFamily == dnsmessage_pb2.PBDNSMessage.INET:
             if msg.HasField('from'):
@@ -146,15 +148,21 @@ class PDNSPBConnHandler(object):
                 iptostr = socket.inet_ntop(socket.AF_INET, msg.to)
         else:
             if msg.HasField('from'):
-                ipfromstr = socket.inet_ntop(socket.AF_INET6, fromvalue)
+                ipfromstr = '[' + socket.inet_ntop(socket.AF_INET6, fromvalue) + ']'
             if msg.HasField('to'):
-                iptostr = socket.inet_ntop(socket.AF_INET6, msg.to)
+                iptostr = '[' + socket.inet_ntop(socket.AF_INET6, msg.to) + ']'
 
         if msg.socketProtocol == dnsmessage_pb2.PBDNSMessage.UDP:
             protostr = 'UDP'
         else:
             protostr = 'TCP'
 
+        if msg.HasField('fromPort'):
+            fromportstr = ':' + str(msg.fromPort) + ' '
+
+        if msg.HasField('toPort'):
+            toportstr = ':' + str(msg.toPort) + ' '
+
         messageidstr = binascii.hexlify(bytearray(msg.messageId))
 
         serveridstr = 'N/A'
@@ -176,13 +184,15 @@ class PDNSPBConnHandler(object):
         if (msg.HasField('newlyObservedDomain')):
             nod = msg.newlyObservedDomain
 
-        print('[%s] %s of size %d: %s%s -> %s (%s), id: %d, uuid: %s%s '
+        print('[%s] %s of size %d: %s%s%s -> %s%s (%s), id: %d, uuid: %s%s '
                   'requestorid: %s deviceid: %s serverid: %s nod: %d' % (datestr,
                                                     typestr,
                                                     msg.inBytes,
                                                     ipfromstr,
+                                                    fromportstr,
                                                     requestorstr,
                                                     iptostr,
+                                                    toportstr,
                                                     protostr,
                                                     msg.id,
                                                     messageidstr,
index 49e74b4afc048a5bcb024812d9ca8e0e4d5966fc..6cb2653cc10069508698a12cf90e540440dbc582 100644 (file)
@@ -144,6 +144,11 @@ in this array will be logged in PowerDNS at loglevel ``info`` (6).
 Methods
 ^^^^^^^
 
+Methods required for different features
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+:Always required: ``initialize``, ``lookup``
+:Master operation: ``list``, ``getUpdatedMasters``, ``setNotified``
+
 ``initialize``
 ~~~~~~~~~~~~~~
 
@@ -958,12 +963,13 @@ Alternative response
 
 ``createSlaveDomain``
 ~~~~~~~~~~~~~~~~~~~~~
-
 Creates new domain. This method is called when NOTIFY is received and
 you are superslaving.
 
-Mandatory: No Parameters: ip, domain Optional parameters: nameserver,
-account Reply: true for success, false for failure
+ - Mandatory: No
+ - Parameters: ip, domain
+ - Optional parameters: nameserver, account
+ - Reply: true for success, false for failure
 
 Example JSON/RPC
 ''''''''''''''''
@@ -1516,7 +1522,7 @@ Used to find out any updates to master domains. This is used to trigger notifica
 
 -  Mandatory: no
 -  Parameters: none
--  Reply: array of DomainInfo
+-  Reply: array of DomainInfo or at least the ``id``, ``zone``, ``serial`` and ``notified_serial`` fields
 
 Example JSON/RPC
 ''''''''''''''''
index c5e4b328ca2077362615649498b45cc47c819dda..8bd0822467094e4dd6a041f9e0a33df34af8671c 100644 (file)
@@ -227,3 +227,14 @@ memory used for the signature caches. In addition, on startup or
 AXFR-serving, a lot of signing needs to happen.
 
 Most best practices are documented in :rfc:`6781`.
+
+Some notes on TTL usage
+-----------------------
+
+In zones signed by PowerDNS (so non-presigned zones), some TTL values need to be filled in by PowerDNS.
+The TTL of RRSIG record sets is the TTL of the covered RRset.
+For CDS, CDNSKEY, DNSKEY, NSEC, NSEC3 and NSEC3PARAM, we use the SOA default TTL (the last number in the SOA record).
+Except for CDS/CDNSKEY/DNSKEY, these TTLs are chosen because `RFC 4034 <https://tools.ietf.org/html/rfc4034>`__ demands it so.
+
+If you want a 'normal' TTL (3600, 86400, etc.) for your DNSKEY but a low TTL on negative answers, set your SOA default TTL to the high number, and set the TTL on the SOA record itself to the low TTL you want for negative answers.
+Note that the NSEC/NSEC3 records proving those negatives will get the high TTL in that case, and this may affect subsequent resolution in resolvers that do aggressive NSEC caching (`RFC 8198 <https://tools.ietf.org/html/rfc8198>`__).
\ No newline at end of file
index 430f5df842ac3159a2e313572cbdfa9064e09e37..a50ffc64457b32840a0de069eb303651ef3a6b61 100644 (file)
@@ -1,4 +1,4 @@
-@       86400   IN  SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2019121201 10800 3600 604800 10800
+@       86400   IN  SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2020011600 10800 3600 604800 10800
 @       3600    IN  NS  pdns-public-ns1.powerdns.com.
 @       3600    IN  NS  pdns-public-ns2.powerdns.com.
 
@@ -205,7 +205,8 @@ recursor-4.2.1.security-status                          60 IN TXT "1 OK"
 recursor-4.3.0-alpha1.security-status                   60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
 recursor-4.3.0-alpha2.security-status                   60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
 recursor-4.3.0-alpha3.security-status                   60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
-recursor-4.3.0-beta1.security-status                    60 IN TXT "1 OK"
+recursor-4.3.0-beta1.security-status                    60 IN TXT "2 Unsupported pre-release (no known vulnerabilities)"
+recursor-4.3.0-beta2.security-status                    60 IN TXT "1 OK"
 
 ; Recursor Debian
 recursor-3.6.2-2.debian.security-status                 60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/3/security/powerdns-advisory-2015-01/ and https://doc.powerdns.com/3/security/powerdns-advisory-2016-02/"
index 686a6c7bdfbfd6b6c5d9a2f6da6dd1d1eae3d7d0..ca2154f6a4ab6e7fef28ac5a6fae68d4e555bbc3 100644 (file)
@@ -2,6 +2,8 @@
 
 namespace YaHTTP {
 
+  template class AsyncLoader<Request>;
+
   bool isspace(char c) {
     return std::isspace(c) != 0;
   }
index e2052d83f244ee75653b4122b1533499f3d921be..7ca9d42230b8017cd2fe7a4d2290b09a93c3f793 100644 (file)
@@ -35,7 +35,7 @@
 class SPgSQLStatement: public SSqlStatement
 {
 public:
-  SPgSQLStatement(const string& query, bool dolog, int nparams, SPgSQL* db, unsigned int nstatement) {
+  SPgSQLStatement(const string& query, bool dolog, int nparams, SPgSQL* db) {
     d_query = query;
     d_dolog = dolog;
     d_parent = db;
@@ -45,7 +45,6 @@ public:
     d_res_set = NULL;
     paramValues = NULL;
     paramLengths = NULL;
-    d_nstatement = nstatement;
     d_paridx = 0;
     d_residx = 0;
     d_resnum = 0;
@@ -81,7 +80,7 @@ public:
       g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": " << d_query << endl;
       d_dtime.set();
     }
-    d_res_set = PQexecPrepared(d_db(), d_stmt.c_str(), d_nparams, paramValues, paramLengths, NULL, 0);
+    d_res_set = PQexecParams(d_db(), d_query.c_str(), d_nparams, NULL, paramValues, paramLengths, NULL, 0);
     ExecStatusType status = PQresultStatus(d_res_set);
     if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_NONFATAL_ERROR) {
       string errmsg(PQresultErrorMessage(d_res_set));
@@ -208,26 +207,10 @@ private:
   void releaseStatement() {
     d_prepared = false;
     reset();
-    if (!d_stmt.empty()) {
-      string cmd = string("DEALLOCATE " + d_stmt);
-      PGresult *res = PQexec(d_db(), cmd.c_str());
-      PQclear(res);
-      d_stmt.clear();
-    }
   }
 
   void prepareStatement() {
     if (d_prepared) return;
-    // prepare a statement; name must be unique per session (using d_nstatement to ensure this).
-    this->d_stmt = string("stmt") + std::to_string(d_nstatement);
-    PGresult* res = PQprepare(d_db(), d_stmt.c_str(), d_query.c_str(), d_nparams, NULL);
-    ExecStatusType status = PQresultStatus(res);
-    string errmsg(PQresultErrorMessage(res));
-    PQclear(res);
-    if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_NONFATAL_ERROR) {
-      releaseStatement();
-      throw SSqlException("Fatal error during prepare: " + d_query + string(": ") + errmsg);
-    }
     paramValues=NULL;
     d_cur_set=d_paridx=d_residx=d_resnum=d_fnum=0;
     paramLengths=NULL;
@@ -245,7 +228,6 @@ private:
   }
 
   string d_query;
-  string d_stmt;
   SPgSQL *d_parent;
   PGresult *d_res_set;
   PGresult *d_res;
@@ -260,7 +242,6 @@ private:
   int d_resnum;
   int d_fnum;
   int d_cur_set;
-  unsigned int d_nstatement;
 };
 
 bool SPgSQL::s_dolog;
@@ -271,7 +252,6 @@ SPgSQL::SPgSQL(const string &database, const string &host, const string& port, c
   d_db=0;
   d_in_trx = false;
   d_connectstr="";
-  d_nstatement = 0;
 
   if (!database.empty())
     d_connectstr+="dbname="+database;
@@ -338,8 +318,7 @@ void SPgSQL::execute(const string& query)
 
 std::unique_ptr<SSqlStatement> SPgSQL::prepare(const string& query, int nparams)
 {
-  d_nstatement++;
-  return std::unique_ptr<SSqlStatement>(new SPgSQLStatement(query, s_dolog, nparams, this, d_nstatement));
+  return std::unique_ptr<SSqlStatement>(new SPgSQLStatement(query, s_dolog, nparams, this));
 }
 
 void SPgSQL::startTransaction() {
index 35631e45df4d013ced7b89fb0d5b5531e9f8b4f1..91720e08c547ab722ec0f591f7d36a32121a2f61 100644 (file)
@@ -55,7 +55,6 @@ private:
   string d_connectlogstr;
   static bool s_dolog;
   bool d_in_trx;
-  unsigned int d_nstatement;
 };
 
 #endif /* SPGSQL_HH */
index e64d160ec5746aa66dcdecd00e10932bc6c62d7a..dfa28a7e09e2566f7e9f9edea07ce29f12dd01bc 100644 (file)
@@ -518,11 +518,9 @@ bool LMDBBackend::deleteDomain(const DNSName &domain)
 
 bool LMDBBackend::list(const DNSName &target, int id, bool include_disabled)
 {
-  d_inlist=true;
   DomainInfo di;
   {
     auto dtxn = d_tdomains->getROTransaction();
-    
     if((di.id = dtxn.get<0>(target, di))) 
       ; //      cout<<"Found domain "<<target<<" on domain_id "<<di.id <<", list requested "<<id<<endl;
     else {
@@ -530,22 +528,20 @@ bool LMDBBackend::list(const DNSName &target, int id, bool include_disabled)
       return false;
     }
   }
-  
+
   d_rotxn = getRecordsROTransaction(di.id, d_rwtxn);
+  d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
+
   compoundOrdername co;
   d_matchkey = co(di.id);
-  d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
+
   MDBOutVal key, val;
-  d_inlist = true;
-  
   if(d_getcursor->lower_bound(d_matchkey, key, val) || key.get<StringView>().rfind(d_matchkey, 0) != 0) {
     // cout<<"Found nothing for list"<<endl;
     d_getcursor.reset();
-    return true;
   }
-  
-  d_lookupqname = target;
-  
+
+  d_lookupdomain = target;
   return true;
 }
 
@@ -555,6 +551,7 @@ void LMDBBackend::lookup(const QType &type, const DNSName &qdomain, int zoneId,
     g_log << Logger::Warning << "Got lookup for "<<qdomain<<"|"<<type.getName()<<" in zone "<< zoneId<<endl;
     d_dtime.set();
   }
+
   DNSName hunt(qdomain);
   DomainInfo di;
   if(zoneId < 0) {
@@ -586,12 +583,11 @@ void LMDBBackend::lookup(const QType &type, const DNSName &qdomain, int zoneId,
   d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
   MDBOutVal key, val;
   if(type.getCode() == QType::ANY) {
-    d_matchkey = co(zoneId,relqname);
+    d_matchkey = co(zoneId, relqname);
   }
   else {
-    d_matchkey= co(zoneId,relqname, type.getCode());
+    d_matchkey= co(zoneId, relqname, type.getCode());
   }
-  d_inlist=false;
   
   if(d_getcursor->lower_bound(d_matchkey, key, val) || key.get<StringView>().rfind(d_matchkey, 0) != 0) {
     d_getcursor.reset();
@@ -604,146 +600,68 @@ void LMDBBackend::lookup(const QType &type, const DNSName &qdomain, int zoneId,
   if(d_dolog) {
     g_log<<Logger::Warning<< "Query "<<((long)(void*)this)<<": "<<d_dtime.udiffNoReset()<<" usec to execute"<<endl;
   }
-    
-  d_lookuptype=type;
-  d_lookupqname = qdomain;
+
   d_lookupdomain = hunt;
-  d_lookupdomainid = zoneId;
 }
 
-bool LMDBBackend::get(DNSZoneRecord& rr)
-{
-  if(d_inlist)
-    return get_list(rr);
-  else
-    return get_lookup(rr);
-}
 
-bool LMDBBackend::get(DNSResourceRecord& rr)
-{
-  //  cout <<"Old-school get called"<<endl;
-  DNSZoneRecord dzr;
-  if(d_inlist) {
-    if(!get_list(dzr))
-      return false;
-  }
-  else {
-    if(!get_lookup(dzr))
-      return false;
-  }
-  rr.qname = dzr.dr.d_name;
-  rr.ttl = dzr.dr.d_ttl;
-  rr.qtype =dzr.dr.d_type;
-  rr.content = dzr.dr.d_content->getZoneRepresentation(true);
-  rr.domain_id = dzr.domain_id;
-  rr.auth = dzr.auth;
-  //  cout<<"old school called for "<<rr.qname<<", "<<rr.qtype.getName()<<endl;
-  return true;
-}
-
-bool LMDBBackend::getSOA(const DNSName &domain, SOAData &sd)
-{
-  //  cout <<"Native getSOA called"<<endl;
-  lookup(QType(QType::SOA), domain, -1);
-  DNSZoneRecord dzr;
-  bool found=false;
-  while(get(dzr)) {
-    auto src = getRR<SOARecordContent>(dzr.dr);
-    sd.domain_id = dzr.domain_id;
-    sd.ttl = dzr.dr.d_ttl;
-    sd.qname = dzr.dr.d_name;
-    
-    sd.nameserver = src->d_mname;
-    sd.hostmaster = src->d_rname;
-    sd.serial = src->d_st.serial;
-    sd.refresh = src->d_st.refresh;
-    sd.retry = src->d_st.retry;
-    sd.expire = src->d_st.expire;
-    sd.default_ttl = src->d_st.minimum;
-    
-    sd.db = this;
-    found=true;
-  }
-  return found;
-}
-bool LMDBBackend::get_list(DNSZoneRecord& rr)
+bool LMDBBackend::get(DNSZoneRecord& rr)
 {
   for(;;) {
-    if(!d_getcursor)  {
+    if(!d_getcursor) {
       d_rotxn.reset();
       return false;
     }
-    
-    MDBOutVal keyv, val;
 
+    MDBOutVal keyv, val;
     d_getcursor->current(keyv, val);
+
     DNSResourceRecord drr;
     serFromString(val.get<string>(), drr);
-  
+
     auto key = keyv.get<string_view>();
-    rr.dr.d_name = compoundOrdername::getQName(key) + d_lookupqname;
-    rr.domain_id = compoundOrdername::getDomainID(key);
     rr.dr.d_type = compoundOrdername::getQType(key).getCode();
-    rr.dr.d_ttl = drr.ttl;
-    rr.auth = drr.auth;
-  
+
     if(rr.dr.d_type == QType::NSEC3) {
-      //      cout << "Had a magic NSEC3, skipping it" << endl;
+      // Hit a magic NSEC3 skipping
       if(d_getcursor->next(keyv, val) || keyv.get<StringView>().rfind(d_matchkey, 0) != 0) {
         d_getcursor.reset();
       }
+
       continue;
     }
+
+    rr.dr.d_name = compoundOrdername::getQName(key) + d_lookupdomain;
+    rr.domain_id = compoundOrdername::getDomainID(key);
+    rr.dr.d_ttl = drr.ttl;
     rr.dr.d_content = unserializeContentZR(rr.dr.d_type, rr.dr.d_name, drr.content);
-    
+    rr.auth = drr.auth;
+
     if(d_getcursor->next(keyv, val) || keyv.get<StringView>().rfind(d_matchkey, 0) != 0) {
       d_getcursor.reset();
     }
+
     break;
   }
+
   return true;
 }
 
 
-bool LMDBBackend::get_lookup(DNSZoneRecord& rr)
+bool LMDBBackend::get(DNSResourceRecord& rr)
 {
-  for(;;) {
-    if(!d_getcursor) {
-      d_rotxn.reset();
-      return false;
-    }
-    MDBOutVal keyv, val;
-    d_getcursor->current(keyv, val);
-    DNSResourceRecord drr;
-    serFromString(val.get<string>(), drr);
-    
-    auto key = keyv.get<string_view>();
-    
-    rr.dr.d_name = compoundOrdername::getQName(key) + d_lookupdomain;
-    
-    rr.domain_id = compoundOrdername::getDomainID(key);
-    //  cout << "We found "<<rr.qname<< " in zone id "<<rr.domain_id <<endl;
-    rr.dr.d_type = compoundOrdername::getQType(key).getCode();
-    rr.dr.d_ttl = drr.ttl;
-    if(rr.dr.d_type == QType::NSEC3) {
-      //      cout << "Hit a magic NSEC3 skipping" << endl;
-      if(d_getcursor->next(keyv, val) || keyv.get<StringView>().rfind(d_matchkey, 0) != 0) {
-        d_getcursor.reset();
-        d_rotxn.reset();
-      }
-      continue;
-    }
-    
-    rr.dr.d_content = unserializeContentZR(rr.dr.d_type, rr.dr.d_name, drr.content);
-    rr.auth = drr.auth;
-    if(d_getcursor->next(keyv, val) || keyv.get<StringView>().rfind(d_matchkey, 0) != 0) {
-      d_getcursor.reset();
-      d_rotxn.reset();
-    }
-    break;
+  DNSZoneRecord dzr;
+  if(!get(dzr)) {
+    return false;
   }
 
-  
+  rr.qname = dzr.dr.d_name;
+  rr.ttl = dzr.dr.d_ttl;
+  rr.qtype = dzr.dr.d_type;
+  rr.content = dzr.dr.d_content->getZoneRepresentation(true);
+  rr.domain_id = dzr.domain_id;
+  rr.auth = dzr.auth;
+
   return true;
 }
 
index 5918067edf6e71085eaab998873d6d74419c03c3..b01a849981378a0cb43cac75f7c2ee87d93b72bb 100644 (file)
@@ -55,7 +55,6 @@ public:
   bool get(DNSResourceRecord &rr) override;
   bool get(DNSZoneRecord& dzr) override;
 
-  bool getSOA(const DNSName &domain, SOAData &sd) override;
   void getUnfreshSlaveInfos(vector<DomainInfo>* domains) override;
   
   bool setMaster(const DNSName &domain, const string &ip) override;
@@ -259,11 +258,7 @@ private:
   
   bool get_list(DNSZoneRecord &rr);
   bool get_lookup(DNSZoneRecord &rr);
-  bool d_inlist{false};
-  QType d_lookuptype;                   // for get after lookup
   std::string d_matchkey;
-  int32_t d_lookupdomainid;            // for get after lookup
-  DNSName d_lookupqname;
   DNSName d_lookupdomain;
   
   DNSName d_transactiondomain;
index 6d7f34437f78b6410377a55ebb871af4b87a7975..2c20cd306a678bddea867ba3ea95a118b5542b21 100644 (file)
@@ -904,6 +904,13 @@ void RemoteBackend::getAllDomains(vector<DomainInfo> *domains, bool include_disa
   }
 }
 
+void RemoteBackend::alsoNotifies(const DNSName &domain, set<string> *ips)
+{
+  std::vector<std::string> meta;
+  getDomainMetadata(domain, "ALSO-NOTIFY", meta);
+  ips->insert(meta.begin(), meta.end());
+}
+
 void RemoteBackend::getUpdatedMasters(vector<DomainInfo>* domains)
 {
   Json query = Json::object{
index c453c52e99e002ae5cb3c981feedda911ad7cb7c..647da8cdd953744bf60cc6d9f883471aeff465a9 100644 (file)
@@ -189,6 +189,7 @@ class RemoteBackend : public DNSBackend
   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;
+  void alsoNotifies(const DNSName &domain, set<string> *ips) override;
 
   static DNSBackend *maker();
 
index acabae29336a86ec513cda617d410f7ea0fb5bd8..13d803b560450d5378d6bd17a09752235a000a91 100644 (file)
@@ -89,6 +89,17 @@ BOOST_AUTO_TEST_CASE(test_method_setDomainMetadata) {
    BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"TEST", meta));
 }
 
+BOOST_AUTO_TEST_CASE(test_method_alsoNotifies) {
+   BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"ALSO-NOTIFY", {"192.0.2.1"}));
+   std::set<std::string> alsoNotifies;
+   BOOST_TEST_MESSAGE("Testing alsoNotifies method");
+   be->alsoNotifies(DNSName("unit.test."), &alsoNotifies);
+   BOOST_CHECK_EQUAL(alsoNotifies.size(), 1);
+   if (alsoNotifies.size() > 0)
+      BOOST_CHECK_EQUAL(alsoNotifies.count("192.0.2.1"), 1);
+   BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"ALSO-NOTIFY", std::vector<std::string>()));
+}
+
 BOOST_AUTO_TEST_CASE(test_method_getDomainMetadata) {
    std::vector<std::string> meta;
    BOOST_TEST_MESSAGE("Testing getDomainMetadata method");
index 48b937acf31338535ac8c2b7f36e6a26ea99104c..094c1f63ba1c15aae7fdca130378d57c797d3ed7 100644 (file)
@@ -154,6 +154,7 @@ void AuthPacketCache::insert(DNSPacket& q, DNSPacket& r, unsigned int maxTTL)
         continue;
       }
 
+      moveCacheItemToBack<SequencedTag>(mc.d_map, iter);
       iter->value = entry.value;
       iter->ttd = now + ourttl;
       iter->created = now;
@@ -162,7 +163,15 @@ void AuthPacketCache::insert(DNSPacket& q, DNSPacket& r, unsigned int maxTTL)
 
     /* no existing entry found to refresh */
     mc.d_map.insert(entry);
-    (*d_statnumentries)++;
+
+    if (*d_statnumentries >= d_maxEntries) {
+      /* remove the least recently inserted or replaced entry */
+      auto& sidx = mc.d_map.get<SequencedTag>();
+      sidx.pop_front();
+    }
+    else {
+      (*d_statnumentries)++;
+    }
   }
 }
 
index e0191dc95008bede5c077d6d5a70a6110a4a8563..91b41528bcc1c09562dcb445093a2f560df429b1 100644 (file)
@@ -98,6 +98,8 @@ private:
     indexed_by <
       hashed_non_unique<tag<HashTag>, member<CacheEntry,uint32_t,&CacheEntry::hash> >,
       ordered_non_unique<tag<NameTag>, member<CacheEntry,DNSName,&CacheEntry::qname>, CanonDNSNameCompare >,
+      /* Note that this sequence holds 'least recently inserted or replaced', not least recently used.
+         Making it a LRU would require taking a write-lock when fetching from the cache, making the RW-lock inefficient compared to a mutex */
       sequenced<tag<SequencedTag>>
       >
     > cmap_t;
index 2dc8c3254b1c950feabfdedce286aa5dcfe90857..e790e9e16523ec67c9ee30efa9160164ab8167bf 100644 (file)
@@ -109,9 +109,17 @@ void AuthQueryCache::insert(const DNSName &qname, const QType& qtype, const vect
 
     if (!inserted) {
       mc.d_map.replace(place, val);
+      moveCacheItemToBack<SequencedTag>(mc.d_map, place);
     }
     else {
-      (*d_statnumentries)++;
+      if (*d_statnumentries >= d_maxEntries) {
+        /* remove the least recently inserted or replaced entry */
+        auto& sidx = mc.d_map.get<SequencedTag>();
+        sidx.pop_front();
+      }
+      else {
+        (*d_statnumentries)++;
+      }
     }
   }
 }
index 193f91bf92b8d37080d755d1557be46c5c6c41c0..2dd27c835d9e4bae4e87e39f3a27d42e8f82aedc 100644 (file)
@@ -80,6 +80,8 @@ private:
                                                          member<CacheEntry,uint16_t,&CacheEntry::qtype>,
                                                          member<CacheEntry,int, &CacheEntry::zoneID> > > ,
       ordered_non_unique<tag<NameTag>, member<CacheEntry,DNSName,&CacheEntry::qname>, CanonDNSNameCompare >,
+      /* Note that this sequence holds 'least recently inserted or replaced', not least recently used.
+         Making it a LRU would require taking a write-lock when fetching from the cache, making the RW-lock inefficient compared to a mutex */
       sequenced<tag<SequencedTag>>
                            >
   > cmap_t;
index 9b97b5e9a8a470f015701b8b05a7793a7be1de27..597a238ea313d8ac3295393684b31bd6660993f0 100644 (file)
@@ -446,14 +446,13 @@ try
         "', do = " <<question.d_dnssecOk <<", bufsize = "<< question.getMaxReplyLen();
       if(question.d_ednsRawPacketSizeLimit > 0 && question.getMaxReplyLen() != (unsigned int)question.d_ednsRawPacketSizeLimit)
         g_log<<" ("<<question.d_ednsRawPacketSizeLimit<<")";
-      g_log<<": ";
     }
 
     if(PC.enabled() && (question.d.opcode != Opcode::Notify && question.d.opcode != Opcode::Update) && question.couldBeCached()) {
       bool haveSomething=PC.get(question, cached); // does the PacketCache recognize this question?
       if (haveSomething) {
         if(logDNSQueries)
-          g_log<<"packetcache HIT"<<endl;
+          g_log<<"packetcache HIT"<<endl;
         cached.setRemote(&question.d_remote);  // inlined
         cached.setSocket(question.getSocket());                               // inlined
         cached.d_anyLocal = question.d_anyLocal;
@@ -469,14 +468,19 @@ try
     }
 
     if(distributor->isOverloaded()) {
-      if(logDNSQueries) 
-        g_log<<"Dropped query, backends are overloaded"<<endl;
+      if(logDNSQueries)
+        g_log<<"Dropped query, backends are overloaded"<<endl;
       overloadDrops++;
       continue;
     }
-        
-    if(PC.enabled() && logDNSQueries)
-      g_log<<"packetcache MISS"<<endl;
+
+    if (logDNSQueries) {
+      if (PC.enabled()) {
+        g_log<<": packetcache MISS"<<endl;
+      } else {
+        g_log<<endl;
+      }
+    }
 
     try {
       distributor->question(question, &sendout); // otherwise, give to the distributor
@@ -534,6 +538,10 @@ void mainthread()
    PC.setMaxEntries(::arg().asNum("max-packet-cache-entries"));
    QC.setMaxEntries(::arg().asNum("max-cache-entries"));
 
+   if (!PC.enabled() && ::arg().mustDo("log-dns-queries")) {
+     g_log<<Logger::Warning<<"Packet cache disabled, logging queries without HIT/MISS"<<endl;
+   }
+
    stubParseResolveConf();
 
    if(!::arg()["chroot"].empty()) {
index 98af2e3fe775950722b54df430322768841adbb3..6c0f78205dac755d1d325aa8fc42a4621b4620f3 100644 (file)
@@ -402,7 +402,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "getQueryCounters", true, "[max=10]", "show current buffer of query counters, limited by 'max' if provided" },
   { "getResponseRing", true, "", "return the current content of the response ring" },
   { "getRespRing", true, "", "return the qname/rcode content of the response ring" },
-  { "getServer", true, "n", "returns server with index n" },
+  { "getServer", true, "id", "returns server with index 'n' or whose uuid matches if 'id' is an UUID string" },
   { "getServers", true, "", "returns a table with all defined servers" },
   { "getStatisticsCounters", true, "", "returns a map of statistic counters" },
   { "getTLSContext", true, "n", "returns the TLS context with index n" },
@@ -488,7 +488,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "rmResponseRule", true, "id", "remove response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
   { "rmRule", true, "id", "remove rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
   { "rmSelfAnsweredResponseRule", true, "id", "remove self-answered response rule in position 'id', or whose uuid matches if 'id' is an UUID string" },
-  { "rmServer", true, "n", "remove server with index n" },
+  { "rmServer", true, "id", "remove server with index 'id' or whose uuid matches if 'id' is an UUID string" },
   { "roundrobin", false, "", "Simple round robin over available servers" },
   { "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" },
@@ -569,8 +569,8 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
   { "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
   { "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
-  { "SpoofAction", true, "{ip, ...} ", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
-  { "SpoofCNAMEAction", true, "cname", "Forge a response with the specified CNAME value" },
+  { "SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
+  { "SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value" },
   { "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" },
   { "TagAction", true, "name, value", "set the tag named 'name' to the given value" },
   { "TagResponseAction", true, "name, value", "set the tag named 'name' to the given value" },
index 118e70e8af6afa8bb7c8f313ea7d95f0085ad587..31bc9e79648b7ebbb571b2f7a88b9b902b2e0263 100644 (file)
@@ -149,6 +149,67 @@ private:
     bool d_enabled{false};
   };
 
+  struct DynBlockRatioRule: DynBlockRule
+  {
+    DynBlockRatioRule(): DynBlockRule()
+    {
+    }
+
+    DynBlockRatioRule(const std::string& blockReason, unsigned int blockDuration, double ratio, double warningRatio, unsigned int seconds, DNSAction::Action action, size_t minimumNumberOfResponses): DynBlockRule(blockReason, blockDuration, 0, 0, seconds, action), d_minimumNumberOfResponses(minimumNumberOfResponses), d_ratio(ratio), d_warningRatio(warningRatio)
+    {
+    }
+
+    bool ratioExceeded(unsigned int total, unsigned int count) const
+    {
+      if (!d_enabled) {
+        return false;
+      }
+
+      if (total < d_minimumNumberOfResponses) {
+        return false;
+      }
+
+      double allowed = d_ratio * static_cast<double>(total);
+      return (count > allowed);
+    }
+
+    bool warningRatioExceeded(unsigned int total, unsigned int count) const
+    {
+      if (d_warningRate == 0) {
+        return false;
+      }
+
+      if (total < d_minimumNumberOfResponses) {
+        return false;
+      }
+
+      double allowed = d_warningRatio * static_cast<double>(total);
+      return (count > allowed);
+    }
+
+    std::string toString() const
+    {
+      if (!isEnabled()) {
+        return "";
+      }
+
+      std::stringstream result;
+      if (d_action != DNSAction::Action::None) {
+        result << DNSAction::typeToString(d_action) << " ";
+      }
+      else {
+        result << "Apply the global DynBlock action ";
+      }
+      result << "for " << std::to_string(d_blockDuration) << " seconds when over " << std::to_string(d_ratio) << " ratio during the last " << d_seconds << " seconds, reason: '" << d_blockReason << "'";
+
+      return result.str();
+    }
+
+    size_t d_minimumNumberOfResponses{0};
+    double d_ratio{0.0};
+    double d_warningRatio{0.0};
+  };
+
   typedef std::unordered_map<ComboAddress, Counts, ComboAddress::addressOnlyHash, ComboAddress::addressOnlyEqual> counts_t;
 
 public:
@@ -173,6 +234,12 @@ public:
     entry = DynBlockRule(reason, blockDuration, rate, warningRate, seconds, action);
   }
 
+  void setRCodeRatio(uint8_t rcode, double ratio, double warningRatio, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action, size_t minimumNumberOfResponses)
+  {
+    auto& entry = d_rcodeRatioRules[rcode];
+    entry = DynBlockRatioRule(reason, blockDuration, ratio, warningRatio, seconds, action, minimumNumberOfResponses);
+  }
+
   void setQTypeRate(uint16_t qtype, unsigned int rate, unsigned int warningRate, unsigned int seconds, std::string reason, unsigned int blockDuration, DNSAction::Action action)
   {
     auto& entry = d_qtypeRules[qtype];
@@ -201,122 +268,7 @@ public:
     apply(now);
   }
 
-  void apply(const struct timespec& now)
-  {
-    counts_t counts;
-    StatNode statNodeRoot;
-
-    size_t entriesCount = 0;
-    if (hasQueryRules()) {
-      entriesCount += g_rings.getNumberOfQueryEntries();
-    }
-    if (hasResponseRules()) {
-      entriesCount += g_rings.getNumberOfResponseEntries();
-    }
-    counts.reserve(entriesCount);
-
-    processQueryRules(counts, now);
-    processResponseRules(counts, statNodeRoot, now);
-
-    if (counts.empty() && statNodeRoot.empty()) {
-      return;
-    }
-
-    boost::optional<NetmaskTree<DynBlock> > blocks;
-    bool updated = false;
-
-    for (const auto& entry : counts) {
-      const auto& requestor = entry.first;
-      const auto& counters = entry.second;
-
-      if (d_queryRateRule.warningRateExceeded(counters.queries, now)) {
-        handleWarning(blocks, now, requestor, d_queryRateRule, updated);
-      }
-
-      if (d_queryRateRule.rateExceeded(counters.queries, now)) {
-        addBlock(blocks, now, requestor, d_queryRateRule, updated);
-        continue;
-      }
-
-      if (d_respRateRule.warningRateExceeded(counters.respBytes, now)) {
-        handleWarning(blocks, now, requestor, d_respRateRule, updated);
-      }
-
-      if (d_respRateRule.rateExceeded(counters.respBytes, now)) {
-        addBlock(blocks, now, requestor, d_respRateRule, updated);
-        continue;
-      }
-
-      for (const auto& pair : d_qtypeRules) {
-        const auto qtype = pair.first;
-
-        const auto& typeIt = counters.d_qtypeCounts.find(qtype);
-        if (typeIt != counters.d_qtypeCounts.cend()) {
-
-          if (pair.second.warningRateExceeded(typeIt->second, now)) {
-            handleWarning(blocks, now, requestor, pair.second, updated);
-          }
-
-          if (pair.second.rateExceeded(typeIt->second, now)) {
-            addBlock(blocks, now, requestor, pair.second, updated);
-            break;
-          }
-        }
-      }
-
-      for (const auto& pair : d_rcodeRules) {
-        const auto rcode = pair.first;
-
-        const auto& rcodeIt = counters.d_rcodeCounts.find(rcode);
-        if (rcodeIt != counters.d_rcodeCounts.cend()) {
-          if (pair.second.warningRateExceeded(rcodeIt->second, now)) {
-            handleWarning(blocks, now, requestor, pair.second, updated);
-          }
-
-          if (pair.second.rateExceeded(rcodeIt->second, now)) {
-            addBlock(blocks, now, requestor, pair.second, updated);
-            break;
-          }
-        }
-      }
-    }
-
-    if (updated && blocks) {
-      g_dynblockNMG.setState(std::move(*blocks));
-    }
-
-    if (!statNodeRoot.empty()) {
-      StatNode::Stat node;
-      std::unordered_set<DNSName> namesToBlock;
-      statNodeRoot.visit([this,&namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
-                           bool block = false;
-
-                           if (d_smtVisitorFFI) {
-                             dnsdist_ffi_stat_node_t tmp(*node_, self, children);
-                             block = d_smtVisitorFFI(&tmp);
-                           }
-                           else {
-                             block = d_smtVisitor(*node_, self, children);
-                           }
-
-                           if (block) {
-                             namesToBlock.insert(DNSName(node_->fullname));
-                           }
-                         },
-        node);
-
-      if (!namesToBlock.empty()) {
-        updated = false;
-        SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy();
-        for (const auto& name : namesToBlock) {
-          addOrRefreshBlockSMT(smtBlocks, now, name, d_suffixMatchRule, updated);
-        }
-        if (updated) {
-          g_dynblockSMT.setState(std::move(smtBlocks));
-        }
-      }
-    }
-  }
+  void apply(const struct timespec& now);
 
   void excludeRange(const Netmask& range)
   {
@@ -344,6 +296,9 @@ public:
     for (const auto& rule : d_rcodeRules) {
       result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
     }
+    for (const auto& rule : d_rcodeRatioRules) {
+      result << "- " << RCode::to_s(rule.first) << ": " << rule.second.toString() << std::endl;
+    }
     result << "QType rules: " << std::endl;
     for (const auto& rule : d_qtypeRules) {
       result << "- " << QType(rule.first).getName() << ": " << rule.second.toString() << std::endl;
@@ -360,116 +315,11 @@ public:
   }
 
 private:
-  bool checkIfQueryTypeMatches(const Rings::Query& query)
-  {
-    auto rule = d_qtypeRules.find(query.qtype);
-    if (rule == d_qtypeRules.end()) {
-      return false;
-    }
-
-    return rule->second.matches(query.when);
-  }
-
-  bool checkIfResponseCodeMatches(const Rings::Response& response)
-  {
-    auto rule = d_rcodeRules.find(response.dh.rcode);
-    if (rule == d_rcodeRules.end()) {
-      return false;
-    }
-
-    return rule->second.matches(response.when);
-  }
-
-  void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning)
-  {
-    if (d_excludedSubnets.match(requestor)) {
-      /* do not add a block for excluded subnets */
-      return;
-    }
-
-    if (!blocks) {
-      blocks = g_dynblockNMG.getCopy();
-    }
-    struct timespec until = now;
-    until.tv_sec += rule.d_blockDuration;
-    unsigned int count = 0;
-    const auto& got = blocks->lookup(Netmask(requestor));
-    bool expired = false;
-    bool wasWarning = false;
-
-    if (got) {
-      if (warning && !got->second.warning) {
-        /* we have an existing entry which is not a warning,
-           don't override it */
-        return;
-      }
-      else if (!warning && got->second.warning) {
-        wasWarning = true;
-      }
-      else {
-        if (until < got->second.until) {
-          // had a longer policy
-          return;
-        }
-      }
 
-      if (now < got->second.until) {
-        // only inherit count on fresh query we are extending
-        count = got->second.blocks;
-      }
-      else {
-        expired = true;
-      }
-    }
-
-    DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action};
-    db.blocks = count;
-    db.warning = warning;
-    if (!d_beQuiet && (!got || expired || wasWarning)) {
-      warnlog("Inserting %sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
-    }
-    blocks->insert(Netmask(requestor)).second = db;
-    updated = true;
-  }
-
-  void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated)
-  {
-    if (d_excludedDomains.check(name)) {
-      /* do not add a block for excluded domains */
-      return;
-    }
-
-    struct timespec until = now;
-    until.tv_sec += rule.d_blockDuration;
-    unsigned int count = 0;
-    const auto& got = blocks.lookup(name);
-    bool expired = false;
-    DNSName domain(name.makeLowerCase());
-
-    if (got) {
-      if (until < got->until) {
-        // had a longer policy
-        return;
-      }
-
-      if (now < got->until) {
-        // only inherit count on fresh query we are extending
-        count = got->blocks;
-      }
-      else {
-        expired = true;
-      }
-    }
-
-    DynBlock db{rule.d_blockReason, until, domain, rule.d_action};
-    db.blocks = count;
-
-    if (!d_beQuiet && (!got || expired)) {
-      warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, rule.d_blockDuration, rule.d_blockReason);
-    }
-    blocks.add(domain, db);
-    updated = true;
-  }
+  bool checkIfQueryTypeMatches(const Rings::Query& query);
+  bool checkIfResponseCodeMatches(const Rings::Response& response);
+  void addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning);
+  void addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated);
 
   void addBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated)
   {
@@ -488,7 +338,7 @@ private:
 
   bool hasResponseRules() const
   {
-    return d_respRateRule.isEnabled() || !d_rcodeRules.empty();
+    return d_respRateRule.isEnabled() || !d_rcodeRules.empty() || !d_rcodeRatioRules.empty();
   }
 
   bool hasSuffixMatchRules() const
@@ -501,89 +351,11 @@ private:
     return hasQueryRules() || hasResponseRules();
   }
 
-  void processQueryRules(counts_t& counts, const struct timespec& now)
-  {
-    if (!hasQueryRules()) {
-      return;
-    }
-
-    d_queryRateRule.d_cutOff = d_queryRateRule.d_minTime = now;
-    d_queryRateRule.d_cutOff.tv_sec -= d_queryRateRule.d_seconds;
-
-    for (auto& rule : d_qtypeRules) {
-      rule.second.d_cutOff = rule.second.d_minTime = now;
-      rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
-    }
-
-    for (const auto& shard : g_rings.d_shards) {
-      std::lock_guard<std::mutex> rl(shard->queryLock);
-      for(const auto& c : shard->queryRing) {
-        if (now < c.when) {
-          continue;
-        }
-
-        bool qRateMatches = d_queryRateRule.matches(c.when);
-        bool typeRuleMatches = checkIfQueryTypeMatches(c);
-
-        if (qRateMatches || typeRuleMatches) {
-          auto& entry = counts[c.requestor];
-          if (qRateMatches) {
-            entry.queries++;
-          }
-          if (typeRuleMatches) {
-            entry.d_qtypeCounts[c.qtype]++;
-          }
-        }
-      }
-    }
-  }
-
-  void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now)
-  {
-    if (!hasResponseRules() && !hasSuffixMatchRules()) {
-      return;
-    }
-
-    d_respRateRule.d_cutOff = d_respRateRule.d_minTime = now;
-    d_respRateRule.d_cutOff.tv_sec -= d_respRateRule.d_seconds;
-
-    d_suffixMatchRule.d_cutOff = d_suffixMatchRule.d_minTime = now;
-    d_suffixMatchRule.d_cutOff.tv_sec -= d_suffixMatchRule.d_seconds;
-
-    for (auto& rule : d_rcodeRules) {
-      rule.second.d_cutOff = rule.second.d_minTime = now;
-      rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
-    }
-
-    for (const auto& shard : g_rings.d_shards) {
-      std::lock_guard<std::mutex> rl(shard->respLock);
-      for(const auto& c : shard->respRing) {
-        if (now < c.when) {
-          continue;
-        }
-
-        bool respRateMatches = d_respRateRule.matches(c.when);
-        bool suffixMatchRuleMatches = d_suffixMatchRule.matches(c.when);
-        bool rcodeRuleMatches = checkIfResponseCodeMatches(c);
-
-        if (respRateMatches || rcodeRuleMatches) {
-          auto& entry = counts[c.requestor];
-          if (respRateMatches) {
-            entry.respBytes += c.size;
-          }
-          if (rcodeRuleMatches) {
-            entry.d_rcodeCounts[c.dh.rcode]++;
-          }
-        }
-
-        if (suffixMatchRuleMatches) {
-          root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits<unsigned int>::max()) ? -1 : c.dh.rcode), c.size, boost::none);
-        }
-      }
-    }
-  }
+  void processQueryRules(counts_t& counts, const struct timespec& now);
+  void processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now);
 
   std::map<uint8_t, DynBlockRule> d_rcodeRules;
+  std::map<uint8_t, DynBlockRatioRule> d_rcodeRatioRules;
   std::map<uint16_t, DynBlockRule> d_qtypeRules;
   DynBlockRule d_queryRateRule;
   DynBlockRule d_respRateRule;
index 0c3ae18a078ed4a9b890089c47d64f44efa3ea7c..cc714d23d36e1bc31dfe578eed317419f7c9d426 100644 (file)
@@ -306,6 +306,7 @@ public:
   {
     dq->dh->rcode = d_rcode;
     dq->dh->qr = true; // for good measure
+    setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
     return Action::HeaderModify;
   }
   std::string toString() const override
@@ -313,6 +314,7 @@ public:
     return "set rcode "+std::to_string(d_rcode);
   }
 
+  ResponseConfig d_responseConfig;
 private:
   uint8_t d_rcode;
 };
@@ -326,6 +328,7 @@ public:
     dq->dh->rcode = (d_rcode & 0xF);
     dq->ednsRCode = ((d_rcode & 0xFFF0) >> 4);
     dq->dh->qr = true; // for good measure
+    setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
     return Action::HeaderModify;
   }
   std::string toString() const override
@@ -333,6 +336,7 @@ public:
     return "set ercode "+ERCode::to_s(d_rcode);
   }
 
+  ResponseConfig d_responseConfig;
 private:
   uint8_t d_rcode;
 };
@@ -444,8 +448,7 @@ DNSAction::Action SpoofAction::operator()(DNSQuestion* dq, std::string* ruleresu
   char* dest = ((char*)dq->dh) + dq->len;
 
   dq->dh->qr = true; // for good measure
-  dq->dh->ra = dq->dh->rd; // for good measure
-  dq->dh->ad = false;
+  setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
   dq->dh->ancount = 0;
   dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it
 
@@ -1179,6 +1182,7 @@ public:
 
     dq->du->setHTTPResponse(d_code, d_body, d_contentType);
     dq->dh->qr = true; // for good measure
+    setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
     return Action::HeaderModify;
   }
 
@@ -1186,6 +1190,8 @@ public:
   {
     return "return an HTTP status of " + std::to_string(d_code);
   }
+
+  ResponseConfig d_responseConfig;
 private:
   std::string d_body;
   std::string d_contentType;
@@ -1244,6 +1250,42 @@ static void addAction(GlobalStateHolder<vector<T> > *someRulActions, const luadn
     });
 }
 
+typedef std::unordered_map<std::string, boost::variant<bool> > responseParams_t;
+
+static void parseResponseConfig(boost::optional<responseParams_t> vars, ResponseConfig& config)
+{
+  if (vars) {
+    if (vars->count("aa")) {
+      config.setAA = boost::get<bool>((*vars)["aa"]);
+    }
+    if (vars->count("ad")) {
+      config.setAD = boost::get<bool>((*vars)["ad"]);
+    }
+    if (vars->count("ra")) {
+      config.setRA = boost::get<bool>((*vars)["ra"]);
+    }
+  }
+}
+
+void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config)
+{
+  if (config.setAA) {
+    dh.aa = *config.setAA;
+  }
+  if (config.setAD) {
+    dh.ad = *config.setAD;
+  }
+  else {
+    dh.ad = false;
+  }
+  if (config.setRA) {
+    dh.ra = *config.setRA;
+  }
+  else {
+    dh.ra = dh.rd; // for good measure
+  }
+}
+
 void setupLuaActions()
 {
   g_lua.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
@@ -1336,7 +1378,7 @@ void setupLuaActions()
       return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a));
     });
 
-  g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b ) {
+  g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b, boost::optional<responseParams_t> vars ) {
       vector<ComboAddress> addrs;
       if(auto s = boost::get<std::string>(&inp))
         addrs.push_back(ComboAddress(*s));
@@ -1345,13 +1387,23 @@ void setupLuaActions()
         for(const auto& a: v)
           addrs.push_back(ComboAddress(a.second));
       }
-      if(b)
+      if(b) {
         addrs.push_back(ComboAddress(*b));
-      return std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+      }
+
+      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+      auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
+      parseResponseConfig(vars, sa->d_responseConfig);
+      return ret;
     });
 
-  g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a) {
-      return std::shared_ptr<DNSAction>(new SpoofAction(a));
+  g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional<responseParams_t> vars) {
+      auto ret = std::shared_ptr<DNSAction>(new SpoofAction(a));
+      ResponseConfig responseConfig;
+      parseResponseConfig(vars, responseConfig);
+      auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
+      sa->d_responseConfig = responseConfig;
+      return ret;
     });
 
   g_lua.writeFunction("DropAction", []() {
@@ -1386,12 +1438,18 @@ void setupLuaActions()
       return std::shared_ptr<DNSResponseAction>(new LogResponseAction(fname ? *fname : "", append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false));
     });
 
-  g_lua.writeFunction("RCodeAction", [](uint8_t rcode) {
-      return std::shared_ptr<DNSAction>(new RCodeAction(rcode));
+  g_lua.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+      auto ret = std::shared_ptr<DNSAction>(new RCodeAction(rcode));
+      auto rca = std::dynamic_pointer_cast<RCodeAction>(ret);
+      parseResponseConfig(vars, rca->d_responseConfig);
+      return ret;
     });
 
-  g_lua.writeFunction("ERCodeAction", [](uint8_t rcode) {
-      return std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
+  g_lua.writeFunction("ERCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+      auto ret = std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
+      auto erca = std::dynamic_pointer_cast<ERCodeAction>(ret);
+      parseResponseConfig(vars, erca->d_responseConfig);
+      return ret;
     });
 
   g_lua.writeFunction("SkipCacheAction", []() {
@@ -1543,8 +1601,11 @@ void setupLuaActions()
     });
 
 #ifdef HAVE_DNS_OVER_HTTPS
-  g_lua.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType) {
-      return std::shared_ptr<DNSAction>(new HTTPStatusAction(status, body, contentType ? *contentType : ""));
+  g_lua.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType, boost::optional<responseParams_t> vars) {
+      auto ret = std::shared_ptr<DNSAction>(new HTTPStatusAction(status, body, contentType ? *contentType : ""));
+      auto hsa = std::dynamic_pointer_cast<HTTPStatusAction>(ret);
+      parseResponseConfig(vars, hsa->d_responseConfig);
+      return ret;
     });
 #endif /* HAVE_DNS_OVER_HTTPS */
 
index 6a01adf1fc0a2f96ff0ff9bdf05889c0968cb451..2cfb6f61e800cd58800e220f69271dbb6cdca220 100644 (file)
@@ -118,6 +118,7 @@ void setupLuaBindings(bool client)
   );
   g_lua.registerMember("order", &DownstreamState::order);
   g_lua.registerMember("name", &DownstreamState::name);
+  g_lua.registerFunction<std::string(DownstreamState::*)()>("getID", [](const DownstreamState& s) { return boost::uuids::to_string(s.id); });
 
   /* dnsheader */
   g_lua.registerFunction<void(dnsheader::*)(bool)>("setRD", [](dnsheader& dh, bool v) {
@@ -128,6 +129,30 @@ void setupLuaBindings(bool client)
       return (bool)dh.rd;
     });
 
+  g_lua.registerFunction<void(dnsheader::*)(bool)>("setRA", [](dnsheader& dh, bool v) {
+      dh.ra=v;
+    });
+
+  g_lua.registerFunction<bool(dnsheader::*)()>("getRA", [](dnsheader& dh) {
+      return (bool)dh.ra;
+    });
+
+  g_lua.registerFunction<void(dnsheader::*)(bool)>("setAD", [](dnsheader& dh, bool v) {
+      dh.ad=v;
+    });
+
+  g_lua.registerFunction<bool(dnsheader::*)()>("getAD", [](dnsheader& dh) {
+      return (bool)dh.ad;
+    });
+
+  g_lua.registerFunction<void(dnsheader::*)(bool)>("setAA", [](dnsheader& dh, bool v) {
+      dh.aa=v;
+     });
+
+  g_lua.registerFunction<bool(dnsheader::*)()>("getAA", [](dnsheader& dh) {
+      return (bool)dh.aa;
+    });
+
   g_lua.registerFunction<void(dnsheader::*)(bool)>("setCD", [](dnsheader& dh, bool v) {
       dh.cd=v;
     });
index c1f0a1cb801dff34a61d5dc582f5f7d4d6a1a835..0d37ff320c583d27ce214151de2bc9a43abfe125 100644 (file)
@@ -741,6 +741,11 @@ void setupLuaInspection()
         group->setRCodeRate(rcode, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
       }
     });
+  g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint8_t, double, unsigned int, const std::string&, unsigned int, size_t, boost::optional<DNSAction::Action>, boost::optional<double>)>("setRCodeRatio", [](std::shared_ptr<DynBlockRulesGroup>& group, uint8_t rcode, double ratio, unsigned int seconds, const std::string& reason, unsigned int blockDuration, size_t minimumNumberOfResponses, boost::optional<DNSAction::Action> action, boost::optional<double> warningRatio) {
+      if (group) {
+        group->setRCodeRatio(rcode, ratio, warningRatio ? *warningRatio : 0.0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None, minimumNumberOfResponses);
+      }
+    });
   g_lua.registerFunction<void(std::shared_ptr<DynBlockRulesGroup>::*)(uint16_t, unsigned int, unsigned int, const std::string&, unsigned int, boost::optional<DNSAction::Action>, boost::optional<unsigned int>)>("setQTypeRate", [](std::shared_ptr<DynBlockRulesGroup>& group, uint16_t qtype, unsigned int rate, unsigned int seconds, const std::string& reason, unsigned int blockDuration, boost::optional<DNSAction::Action> action, boost::optional<unsigned int> warningRate) {
       if (group) {
         group->setQTypeRate(qtype, rate, warningRate ? *warningRate : 0, seconds, reason, blockDuration, action ? *action : DNSAction::Action::None);
index e85e440c4ff2adb5193baa474fc2911accd9a869..79557c6f96caa5098f796d8779aad5953e789488 100644 (file)
@@ -34,6 +34,7 @@
 #include "dnsdist.hh"
 #include "dnsdist-console.hh"
 #include "dnsdist-ecs.hh"
+#include "dnsdist-healthchecks.hh"
 #include "dnsdist-lua.hh"
 #include "dnsdist-rings.hh"
 #include "dnsdist-secpoll.hh"
@@ -206,15 +207,19 @@ static void parseTLSConfig(TLSConfig& config, const std::string& context, boost:
 
 #endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
 
-void setupLuaConfig(bool client)
+void setupLuaConfig(bool client, bool configCheck)
 {
   typedef std::unordered_map<std::string, boost::variant<bool, std::string, vector<pair<int, std::string> >, DownstreamState::checkfunc_t > > newserver_t;
   g_lua.writeFunction("inClientStartup", [client]() {
         return client && !g_configurationDone;
   });
 
+  g_lua.writeFunction("inConfigCheck", [client, configCheck]() {
+        return !configCheck;
+  });
+
   g_lua.writeFunction("newServer",
-      [client](boost::variant<string,newserver_t> pvars, boost::optional<int> qps) {
+                      [client, configCheck](boost::variant<string,newserver_t> pvars, boost::optional<int> qps) {
       setLuaSideEffect();
 
       std::shared_ptr<DownstreamState> ret = std::make_shared<DownstreamState>(ComboAddress());
@@ -316,11 +321,8 @@ void setupLuaConfig(bool client)
         }
       }
 
-      if(client) {
-        // do not construct DownstreamState now, it would try binding sockets.
-        return ret;
-      }
-      ret=std::make_shared<DownstreamState>(serverAddr, sourceAddr, sourceItf, sourceItfName, numberOfSockets);
+      // create but don't connect the socket in client or check-config modes
+      ret=std::make_shared<DownstreamState>(serverAddr, sourceAddr, sourceItf, sourceItfName, numberOfSockets, !(client || configCheck));
 
       if(vars.count("qps")) {
         int qpsVal=std::stoi(boost::get<string>(vars["qps"]));
@@ -497,19 +499,29 @@ void setupLuaConfig(bool client)
       } );
 
   g_lua.writeFunction("rmServer",
-                      [](boost::variant<std::shared_ptr<DownstreamState>, int> var)
+                      [](boost::variant<std::shared_ptr<DownstreamState>, int, std::string> var)
                       {
                         setLuaSideEffect();
-                        shared_ptr<DownstreamState> server;
-                        auto* rem = boost::get<shared_ptr<DownstreamState>>(&var);
+                        shared_ptr<DownstreamState> server = nullptr;
                         auto states = g_dstates.getCopy();
-                        if(rem) {
+                        if (auto* rem = boost::get<shared_ptr<DownstreamState>>(&var)) {
                           server = *rem;
                         }
+                        else if (auto str = boost::get<std::string>(&var)) {
+                          const auto uuid = getUniqueID(*str);
+                          for (auto& state : states) {
+                            if (state->id == uuid) {
+                              server = state;
+                            }
+                          }
+                        }
                         else {
                           int idx = boost::get<int>(var);
                           server = states.at(idx);
                         }
+                        if (!server) {
+                          throw std::runtime_error("unable to locate the requested server");
+                        }
                         auto localPools = g_pools.getCopy();
                         for (const string& poolName : server->pools) {
                           removeServerFromPool(localPools, poolName, server);
@@ -725,10 +737,25 @@ void setupLuaConfig(bool client)
       return getDownstreamCandidates(g_pools.getCopy(), pool);
     });
 
-  g_lua.writeFunction("getServer", [client](int i) {
-      if (client)
+  g_lua.writeFunction("getServer", [client](boost::variant<unsigned int, std::string> i) {
+      if (client) {
         return std::make_shared<DownstreamState>(ComboAddress());
-      return g_dstates.getCopy().at(i);
+      }
+      auto states = g_dstates.getCopy();
+      if (auto str = boost::get<std::string>(&i)) {
+        const auto uuid = getUniqueID(*str);
+        for (auto& state : states) {
+          if (state->id == uuid) {
+            return state;
+          }
+        }
+      }
+      else if (auto pos = boost::get<unsigned int>(&i)) {
+        return states.at(*pos);
+      }
+
+      g_outputBuffer = "Error: no rule matched\n";
+      return std::shared_ptr<DownstreamState>(nullptr);
     });
 
   g_lua.writeFunction("carbonServer", [](const std::string& address, boost::optional<string> ourName,
@@ -746,7 +773,7 @@ void setupLuaConfig(bool client)
       g_carbon.setState(ours);
   });
 
-  g_lua.writeFunction("webserver", [client](const std::string& address, const std::string& password, const boost::optional<std::string> apiKey, const boost::optional<std::map<std::string, std::string> > customHeaders) {
+  g_lua.writeFunction("webserver", [client,configCheck](const std::string& address, const std::string& password, const boost::optional<std::string> apiKey, const boost::optional<std::map<std::string, std::string> > customHeaders) {
       setLuaSideEffect();
       ComboAddress local;
       try {
@@ -756,7 +783,7 @@ void setupLuaConfig(bool client)
         throw std::runtime_error(std::string("Error parsing the bind address for the webserver: ") + e.reason);
       }
 
-      if (client) {
+      if (client || configCheck) {
         return;
       }
 
@@ -809,11 +836,11 @@ void setupLuaConfig(bool client)
       }
     });
 
-  g_lua.writeFunction("controlSocket", [client](const std::string& str) {
+  g_lua.writeFunction("controlSocket", [client,configCheck](const std::string& str) {
       setLuaSideEffect();
       ComboAddress local(str, 5199);
 
-      if(client) {
+      if(client || configCheck) {
        g_serverControl = local;
        return;
       }
@@ -1268,9 +1295,12 @@ void setupLuaConfig(bool client)
 #endif
     });
 
-  g_lua.writeFunction("generateDNSCryptProviderKeys", [](const std::string& publicKeyFile, const std::string privateKeyFile) {
+  g_lua.writeFunction("generateDNSCryptProviderKeys", [client](const std::string& publicKeyFile, const std::string privateKeyFile) {
       setLuaNoSideEffect();
 #ifdef HAVE_DNSCRYPT
+      if (client) {
+        return;
+      }
       unsigned char publicKey[DNSCRYPT_PROVIDER_PUBLIC_KEY_SIZE];
       unsigned char privateKey[DNSCRYPT_PROVIDER_PRIVATE_KEY_SIZE];
       sodium_mlock(privateKey, sizeof(privateKey));
@@ -1325,8 +1355,11 @@ void setupLuaConfig(bool client)
     });
 
 #ifdef HAVE_DNSCRYPT
-  g_lua.writeFunction("generateDNSCryptCertificate", [](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version) {
+  g_lua.writeFunction("generateDNSCryptCertificate", [client](const std::string& providerPrivateKeyFile, const std::string& certificateFile, const std::string privateKeyFile, uint32_t serial, time_t begin, time_t end, boost::optional<DNSCryptExchangeVersion> version) {
       setLuaNoSideEffect();
+      if (client) {
+        return;
+      }
       DNSCryptPrivateKey privateKey;
       DNSCryptCert cert;
 
@@ -1632,8 +1665,8 @@ void setupLuaConfig(bool client)
       g_useTCPSinglePipe = flag;
     });
 
-  g_lua.writeFunction("snmpAgent", [client](bool enableTraps, boost::optional<std::string> masterSocket) {
-      if(client)
+  g_lua.writeFunction("snmpAgent", [client,configCheck](bool enableTraps, boost::optional<std::string> masterSocket) {
+      if(client || configCheck)
         return;
 #ifdef HAVE_NET_SNMP
       if (g_configurationDone) {
@@ -2128,24 +2161,28 @@ void setupLuaConfig(bool client)
     g_lua.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse=allow; });
 
 #if defined(HAVE_LIBSSL) && defined(HAVE_OCSP_BASIC_SIGN)
-    g_lua.writeFunction("generateOCSPResponse", [](const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin) {
-      return libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin);
+    g_lua.writeFunction("generateOCSPResponse", [client](const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin) {
+      if (client) {
+        return;
+      }
+
+      libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin);
     });
 #endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN*/
 }
 
-vector<std::function<void(void)>> setupLua(bool client, const std::string& config)
+vector<std::function<void(void)>> setupLua(bool client, bool configCheck, const std::string& config)
 {
   g_launchWork= new vector<std::function<void(void)>>();
 
   setupLuaActions();
-  setupLuaConfig(client);
+  setupLuaConfig(client, configCheck);
   setupLuaBindings(client);
   setupLuaBindingsDNSCrypt();
   setupLuaBindingsDNSQuestion();
   setupLuaBindingsKVS(client);
   setupLuaBindingsPacketCache();
-  setupLuaBindingsProtoBuf(client);
+  setupLuaBindingsProtoBuf(client, configCheck);
   setupLuaInspection();
   setupLuaRules();
   setupLuaVars();
index 9bd33266de0eb43eee257ed0f155a9e73990e00b..aa33859c8001f47c956ab50137893c1f70af6f90 100644 (file)
  */
 #pragma once
 
+struct ResponseConfig
+{
+  boost::optional<bool> setAA{boost::none};
+  boost::optional<bool> setAD{boost::none};
+  boost::optional<bool> setRA{boost::none};
+};
+void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config);
+
 class LuaAction : public DNSAction
 {
 public:
@@ -72,6 +80,9 @@ public:
     }
     return ret;
   }
+
+
+  ResponseConfig d_responseConfig;
 private:
   std::vector<ComboAddress> d_addrs;
   DNSName d_cname;
@@ -84,13 +95,14 @@ void parseRuleParams(boost::optional<luaruleparams_t> params, boost::uuids::uuid
 
 typedef NetmaskTree<DynBlock> nmts_t;
 
+vector<std::function<void(void)>> setupLua(bool client, bool configCheck, const std::string& config);
 void setupLuaActions();
 void setupLuaBindings(bool client);
 void setupLuaBindingsDNSCrypt();
 void setupLuaBindingsDNSQuestion();
 void setupLuaBindingsKVS(bool client);
 void setupLuaBindingsPacketCache();
-void setupLuaBindingsProtoBuf(bool client);
+void setupLuaBindingsProtoBuf(bool client, bool configCheck);
 void setupLuaRules();
 void setupLuaInspection();
 void setupLuaVars();
index 3c1b7459f6438c8ec1fa532da955e20e31f5c01b..40f8ac2dc46b0bc7154a2f8ef7cb74c126560e77 100644 (file)
@@ -336,9 +336,8 @@ void TCPClientCollection::addTCPClientThread()
     }
 
     d_tcpclientthreads.push_back(pipefds[1]);
+    ++d_numthreads;
   }
-
-  ++d_numthreads;
 }
 
 static void cleanupClosedTCPConnections()
@@ -1339,7 +1338,9 @@ void tcpAcceptorThread(void* p)
   ComboAddress remote;
   remote.sin4.sin_family = cs->local.sin4.sin_family;
 
-  g_tcpclientthreads->addTCPClientThread();
+  if(!g_tcpclientthreads->hasReachedMaxThreads()) {
+    g_tcpclientthreads->addTCPClientThread();
+  }
 
   auto acl = g_ACL.getLocal();
   for(;;) {
index 07613f131c516b9ee81378c885d38bcf2ec2cdfe..421ce833a3a008489902ca2347f97715b4b6523d 100644 (file)
@@ -20,6 +20,8 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 #include "dnsdist.hh"
+#include "dnsdist-healthchecks.hh"
+
 #include "sstuff.hh"
 #include "ext/json11/json11.hpp"
 #include "ext/incbin/incbin.h"
index 1451363afc4267bd37ae637edfcc88a10e4d0a92..96af451cf7f52905ac9b89ef937312c923e6f37f 100644 (file)
@@ -45,6 +45,7 @@
 #include "dnsdist-cache.hh"
 #include "dnsdist-console.hh"
 #include "dnsdist-ecs.hh"
+#include "dnsdist-healthchecks.hh"
 #include "dnsdist-lua.hh"
 #include "dnsdist-rings.hh"
 #include "dnsdist-secpoll.hh"
@@ -55,7 +56,6 @@
 #include "dolog.hh"
 #include "dnsname.hh"
 #include "dnsparser.hh"
-#include "dnswriter.hh"
 #include "ednsoptions.hh"
 #include "gettime.hh"
 #include "lock.hh"
@@ -84,7 +84,6 @@ struct DNSDistStats g_stats;
 MetricDefinitionStorage g_metricDefinitions;
 
 uint16_t g_maxOutstanding{std::numeric_limits<uint16_t>::max()};
-bool g_verboseHealthChecks{false};
 uint32_t g_staleCacheEntriesTTL{0};
 bool g_syslog{true};
 bool g_allowEmptyResponse{false};
@@ -788,7 +787,7 @@ void DownstreamState::setWeight(int newWeight)
   }
 }
 
-DownstreamState::DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf_, const std::string& sourceItfName_, size_t numberOfSockets): sourceItfName(sourceItfName_), remote(remote_), sourceAddr(sourceAddr_), sourceItf(sourceItf_)
+DownstreamState::DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf_, const std::string& sourceItfName_, size_t numberOfSockets, bool connect=true): sourceItfName(sourceItfName_), remote(remote_), sourceAddr(sourceAddr_), sourceItf(sourceItf_)
 {
   pthread_rwlock_init(&d_lock, nullptr);
   id = getUniqueID();
@@ -801,7 +800,7 @@ DownstreamState::DownstreamState(const ComboAddress& remote_, const ComboAddress
     fd = -1;
   }
 
-  if (!IsAnyAddress(remote)) {
+  if (connect && !IsAnyAddress(remote)) {
     reconnect();
     idStates.resize(g_maxOutstanding);
     sw.start();
@@ -1099,6 +1098,9 @@ bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::s
   case DNSAction::Action::Truncate:
     dq.dh->tc = true;
     dq.dh->qr = true;
+    dq.dh->ra = dq.dh->rd;
+    dq.dh->aa = false;
+    dq.dh->ad = false;
     return true;
     break;
   case DNSAction::Action::HeaderModify:
@@ -1186,6 +1188,9 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const stru
           vinfolog("Query from %s truncated because of dynamic block", dq.remote->toStringWithPort());
           dq.dh->tc = true;
           dq.dh->qr = true;
+          dq.dh->ra = dq.dh->rd;
+          dq.dh->aa = false;
+          dq.dh->ad = false;
           return true;
         }
         else {
@@ -1241,6 +1246,9 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const stru
           vinfolog("Query from %s for %s truncated because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toLogString());
           dq.dh->tc = true;
           dq.dh->qr = true;
+          dq.dh->ra = dq.dh->rd;
+          dq.dh->aa = false;
+          dq.dh->ad = false;
           return true;
         }
         else {
@@ -1834,147 +1842,12 @@ catch(...)
 uint16_t getRandomDNSID()
 {
 #ifdef HAVE_LIBSODIUM
-  return (randombytes_random() % 65536);
+  return randombytes_uniform(65536);
 #else
   return (random() % 65536);
 #endif
 }
 
-static bool upCheck(const shared_ptr<DownstreamState>& ds)
-try
-{
-  DNSName checkName = ds->checkName;
-  uint16_t checkType = ds->checkType.getCode();
-  uint16_t checkClass = ds->checkClass;
-  dnsheader checkHeader;
-  memset(&checkHeader, 0, sizeof(checkHeader));
-
-  checkHeader.qdcount = htons(1);
-  checkHeader.id = getRandomDNSID();
-
-  checkHeader.rd = true;
-  if (ds->setCD) {
-    checkHeader.cd = true;
-  }
-
-  if (ds->checkFunction) {
-    std::lock_guard<std::mutex> lock(g_luamutex);
-    auto ret = ds->checkFunction(checkName, checkType, checkClass, &checkHeader);
-    checkName = std::get<0>(ret);
-    checkType = std::get<1>(ret);
-    checkClass = std::get<2>(ret);
-  }
-
-  vector<uint8_t> packet;
-  DNSPacketWriter dpw(packet, checkName, checkType, checkClass);
-  dnsheader * requestHeader = dpw.getHeader();
-  *requestHeader = checkHeader;
-
-  Socket sock(ds->remote.sin4.sin_family, SOCK_DGRAM);
-  sock.setNonBlocking();
-  if (!IsAnyAddress(ds->sourceAddr)) {
-    sock.setReuseAddr();
-    if (!ds->sourceItfName.empty()) {
-#ifdef SO_BINDTODEVICE
-      int res = setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, ds->sourceItfName.c_str(), ds->sourceItfName.length());
-      if (res != 0 && g_verboseHealthChecks) {
-        infolog("Error setting SO_BINDTODEVICE on the health check socket for backend '%s': %s", ds->getNameWithAddr(), stringerror());
-      }
-#endif
-    }
-    sock.bind(ds->sourceAddr);
-  }
-  sock.connect(ds->remote);
-  ssize_t sent = udpClientSendRequestToBackend(ds, sock.getHandle(), reinterpret_cast<char*>(&packet[0]), packet.size(), true);
-  if (sent < 0) {
-    int ret = errno;
-    if (g_verboseHealthChecks)
-      infolog("Error while sending a health check query to backend %s: %d", ds->getNameWithAddr(), ret);
-    return false;
-  }
-
-  int ret = waitForRWData(sock.getHandle(), true, /* ms to seconds */ ds->checkTimeout / 1000, /* remaining ms to us */ (ds->checkTimeout % 1000) * 1000);
-  if(ret < 0 || !ret) { // error, timeout, both are down!
-    if (ret < 0) {
-      ret = errno;
-      if (g_verboseHealthChecks)
-        infolog("Error while waiting for the health check response from backend %s: %d", ds->getNameWithAddr(), ret);
-    }
-    else {
-      if (g_verboseHealthChecks)
-        infolog("Timeout while waiting for the health check response from backend %s", ds->getNameWithAddr());
-    }
-    return false;
-  }
-
-  string reply;
-  ComboAddress from;
-  sock.recvFrom(reply, from);
-
-  /* we are using a connected socket but hey.. */
-  if (from != ds->remote) {
-    if (g_verboseHealthChecks)
-      infolog("Invalid health check response received from %s, expecting one from %s", from.toStringWithPort(), ds->remote.toStringWithPort());
-    return false;
-  }
-
-  const dnsheader * responseHeader = reinterpret_cast<const dnsheader *>(reply.c_str());
-
-  if (reply.size() < sizeof(*responseHeader)) {
-    if (g_verboseHealthChecks)
-      infolog("Invalid health check response of size %d from backend %s, expecting at least %d", reply.size(), ds->getNameWithAddr(), sizeof(*responseHeader));
-    return false;
-  }
-
-  if (responseHeader->id != requestHeader->id) {
-    if (g_verboseHealthChecks)
-      infolog("Invalid health check response id %d from backend %s, expecting %d", responseHeader->id, ds->getNameWithAddr(), requestHeader->id);
-    return false;
-  }
-
-  if (!responseHeader->qr) {
-    if (g_verboseHealthChecks)
-      infolog("Invalid health check response from backend %s, expecting QR to be set", ds->getNameWithAddr());
-    return false;
-  }
-
-  if (responseHeader->rcode == RCode::ServFail) {
-    if (g_verboseHealthChecks)
-      infolog("Backend %s responded to health check with ServFail", ds->getNameWithAddr());
-    return false;
-  }
-
-  if (ds->mustResolve && (responseHeader->rcode == RCode::NXDomain || responseHeader->rcode == RCode::Refused)) {
-    if (g_verboseHealthChecks)
-      infolog("Backend %s responded to health check with %s while mustResolve is set", ds->getNameWithAddr(), responseHeader->rcode == RCode::NXDomain ? "NXDomain" : "Refused");
-    return false;
-  }
-
-  uint16_t receivedType;
-  uint16_t receivedClass;
-  DNSName receivedName(reply.c_str(), reply.size(), sizeof(dnsheader), false, &receivedType, &receivedClass);
-
-  if (receivedName != checkName || receivedType != checkType || receivedClass != checkClass) {
-    if (g_verboseHealthChecks)
-      infolog("Backend %s responded to health check with an invalid qname (%s vs %s), qtype (%s vs %s) or qclass (%d vs %d)", ds->getNameWithAddr(), receivedName.toLogString(), checkName.toLogString(), QType(receivedType).getName(), QType(checkType).getName(), receivedClass, checkClass);
-    return false;
-  }
-
-  return true;
-}
-catch(const std::exception& e)
-{
-  if (g_verboseHealthChecks)
-    infolog("Error checking the health of backend %s: %s", ds->getNameWithAddr(), e.what());
-  return false;
-}
-catch(...)
-{
-  if (g_verboseHealthChecks)
-    infolog("Unknown exception while checking the health of backend %s", ds->getNameWithAddr());
-  return false;
-}
-
 uint64_t g_maxTCPClientThreads{10};
 std::atomic<uint16_t> g_cacheCleaningDelay{60};
 std::atomic<uint16_t> g_cacheCleaningPercentage{100};
@@ -2071,68 +1944,27 @@ static void healthChecksThread()
 {
   setThreadName("dnsdist/healthC");
 
-  int interval = 1;
+  static const int interval = 1;
 
   for(;;) {
     sleep(interval);
 
-    if(g_tcpclientthreads->getQueuedCount() > 1 && !g_tcpclientthreads->hasReachedMaxThreads())
+    if(g_tcpclientthreads->getQueuedCount() > 1 && !g_tcpclientthreads->hasReachedMaxThreads()) {
       g_tcpclientthreads->addTCPClientThread();
+    }
 
+    auto mplexer = std::shared_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
     auto states = g_dstates.getLocal(); // this points to the actual shared_ptrs!
     for(auto& dss : *states) {
-      if(++dss->lastCheck < dss->checkInterval)
+      if(++dss->lastCheck < dss->checkInterval) {
         continue;
-      dss->lastCheck = 0;
-      if(dss->availability==DownstreamState::Availability::Auto) {
-        bool newState=upCheck(dss);
-        if (newState) {
-          /* check succeeded */
-          dss->currentCheckFailures = 0;
-
-          if (!dss->upStatus) {
-            /* we were marked as down */
-            dss->consecutiveSuccessfulChecks++;
-            if (dss->consecutiveSuccessfulChecks < dss->minRiseSuccesses) {
-              /* if we need more than one successful check to rise
-                 and we didn't reach the threshold yet,
-                 let's stay down */
-              newState = false;
-            }
-          }
-        }
-        else {
-          /* check failed */
-          dss->consecutiveSuccessfulChecks = 0;
-
-          if (dss->upStatus) {
-            /* we are currently up */
-            dss->currentCheckFailures++;
-            if (dss->currentCheckFailures < dss->maxCheckFailures) {
-              /* we need more than one failure to be marked as down,
-                 and we did not reach the threshold yet, let's stay down */
-              newState = true;
-            }
-          }
-        }
-
-        if(newState != dss->upStatus) {
-          warnlog("Marking downstream %s as '%s'", dss->getNameWithAddr(), newState ? "up" : "down");
-
-          if (newState && !dss->connected) {
-            newState = dss->reconnect();
+      }
 
-            if (dss->connected && !dss->threadStarted.test_and_set()) {
-              dss->tid = thread(responderThread, dss);
-            }
-          }
+      dss->lastCheck = 0;
 
-          dss->upStatus = newState;
-          dss->currentCheckFailures = 0;
-          dss->consecutiveSuccessfulChecks = 0;
-          if (g_snmpAgent && g_snmpTrapsEnabled) {
-            g_snmpAgent->sendBackendStatusChangeTrap(dss);
-          }
+      if (dss->availability == DownstreamState::Availability::Auto) {
+        if (!queueHealthCheck(mplexer, dss)) {
+          updateHealthCheckResult(dss, false);
         }
       }
 
@@ -2142,7 +1974,7 @@ static void healthChecksThread()
       dss->prev.queries.store(dss->queries.load());
       dss->prev.reuseds.store(dss->reuseds.load());
       
-      for(IDState& ids  : dss->idStates) { // timeouts
+      for (IDState& ids  : dss->idStates) { // timeouts
         int64_t usageIndicator = ids.usageIndicator;
         if(IDState::isInUse(usageIndicator) && ids.age++ > g_udpTimeout) {
           /* We mark the state as unused as soon as possible
@@ -2177,6 +2009,8 @@ static void healthChecksThread()
         }          
       }
     }
+
+    handleQueuedHealthChecks(mplexer);
   }
 }
 
@@ -2626,7 +2460,7 @@ try
 
   g_policy.setState(leastOutstandingPol);
   if(g_cmdLine.beClient || !g_cmdLine.command.empty()) {
-    setupLua(true, g_cmdLine.config);
+    setupLua(true, false, g_cmdLine.config);
     if (clientAddress != ComboAddress())
       g_serverControl = clientAddress;
     doClient(g_serverControl, g_cmdLine.command);
@@ -2647,13 +2481,13 @@ try
   g_consoleACL.setState(consoleACL);
 
   if (g_cmdLine.checkConfig) {
-    setupLua(true, g_cmdLine.config);
+    setupLua(false, true, g_cmdLine.config);
     // No exception was thrown
     infolog("Configuration '%s' OK!", g_cmdLine.config);
     _exit(EXIT_SUCCESS);
   }
 
-  auto todo=setupLua(false, g_cmdLine.config);
+  auto todo=setupLua(false, false, g_cmdLine.config);
 
   auto localPools = g_pools.getCopy();
   {
@@ -2803,13 +2637,16 @@ try
 
   checkFileDescriptorsLimits(udpBindsCount, tcpBindsCount);
 
+  auto mplexer = std::shared_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
   for(auto& dss : g_dstates.getCopy()) { // it is a copy, but the internal shared_ptrs are the real deal
-    if(dss->availability==DownstreamState::Availability::Auto) {
-      bool newState=upCheck(dss);
-      warnlog("Marking downstream %s as '%s'", dss->getNameWithAddr(), newState ? "up" : "down");
-      dss->upStatus = newState;
+    if (dss->availability == DownstreamState::Availability::Auto) {
+      if (!queueHealthCheck(mplexer, dss, true)) {
+        dss->upStatus = false;
+        warnlog("Marking downstream %s as 'down'", dss->getNameWithAddr());
+      }
     }
   }
+  handleQueuedHealthChecks(mplexer, true);
 
   for(auto& cs : g_frontends) {
     if (cs->dohFrontend != nullptr) {
index d13934d1c698ddc765d0f78af6aa042044dec99a..9c65edc8740b24c9abbb6118e483f9316d4df376 100644 (file)
@@ -839,8 +839,8 @@ struct DownstreamState
 {
    typedef std::function<std::tuple<DNSName, uint16_t, uint16_t>(const DNSName&, uint16_t, uint16_t, dnsheader*)> checkfunc_t;
 
-  DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf, const std::string& sourceItfName, size_t numberOfSockets);
-  DownstreamState(const ComboAddress& remote_): DownstreamState(remote_, ComboAddress(), 0, std::string(), 1) {}
+  DownstreamState(const ComboAddress& remote_, const ComboAddress& sourceAddr_, unsigned int sourceItf, const std::string& sourceItfName, size_t numberOfSockets, bool connect);
+  DownstreamState(const ComboAddress& remote_): DownstreamState(remote_, ComboAddress(), 0, std::string(), 1, true) {}
   ~DownstreamState()
   {
     for (auto& fd : sockets) {
@@ -1158,7 +1158,6 @@ extern size_t g_maxTCPConnectionDuration;
 extern size_t g_maxTCPConnectionsPerClient;
 extern std::atomic<uint16_t> g_cacheCleaningDelay;
 extern std::atomic<uint16_t> g_cacheCleaningPercentage;
-extern bool g_verboseHealthChecks;
 extern uint32_t g_staleCacheEntriesTTL;
 extern bool g_apiReadWrite;
 extern std::string g_apiConfigDirectory;
@@ -1197,7 +1196,6 @@ struct LocalHolders
 struct dnsheader;
 
 void controlThread(int fd, ComboAddress local);
-vector<std::function<void(void)>> setupLua(bool client, const std::string& config);
 std::shared_ptr<ServerPool> getPool(const pools_t& pools, const std::string& poolName);
 std::shared_ptr<ServerPool> createPoolIfNotExists(pools_t& pools, const string& poolName);
 NumberedServerVector getDownstreamCandidates(const pools_t& pools, const std::string& poolName);
index 163d4413802daa3d48c167834e3d5fccc3aa4a47..a4982f27bd79c7ed39974759074521c60cf0a7c1 100644 (file)
@@ -122,8 +122,9 @@ dnsdist_SOURCES = \
        dnsdist-carbon.cc \
        dnsdist-console.cc dnsdist-console.hh \
        dnsdist-dnscrypt.cc \
-       dnsdist-dynblocks.hh \
+       dnsdist-dynblocks.cc dnsdist-dynblocks.hh \
        dnsdist-ecs.cc dnsdist-ecs.hh \
+       dnsdist-healthchecks.cc dnsdist-healthchecks.hh \
        dnsdist-idstate.cc \
        dnsdist-kvs.hh dnsdist-kvs.cc \
        dnsdist-lua.hh dnsdist-lua.cc \
@@ -204,6 +205,7 @@ testrunner_SOURCES = \
        circular_buffer.hh \
        dnsdist.hh \
        dnsdist-cache.cc dnsdist-cache.hh \
+       dnsdist-dynblocks.cc dnsdist-dynblocks.hh \
        dnsdist-ecs.cc dnsdist-ecs.hh \
        dnsdist-kvs.cc dnsdist-kvs.hh \
        dnsdist-rings.hh \
diff --git a/pdns/dnsdistdist/dnsdist-dynblocks.cc b/pdns/dnsdistdist/dnsdist-dynblocks.cc
new file mode 100644 (file)
index 0000000..808d56b
--- /dev/null
@@ -0,0 +1,340 @@
+
+#include "dnsdist.hh"
+#include "dnsdist-dynblocks.hh"
+
+void DynBlockRulesGroup::apply(const struct timespec& now)
+{
+  counts_t counts;
+  StatNode statNodeRoot;
+
+  size_t entriesCount = 0;
+  if (hasQueryRules()) {
+    entriesCount += g_rings.getNumberOfQueryEntries();
+  }
+  if (hasResponseRules()) {
+    entriesCount += g_rings.getNumberOfResponseEntries();
+  }
+  counts.reserve(entriesCount);
+
+  processQueryRules(counts, now);
+  processResponseRules(counts, statNodeRoot, now);
+
+  if (counts.empty() && statNodeRoot.empty()) {
+    return;
+  }
+
+  boost::optional<NetmaskTree<DynBlock> > blocks;
+  bool updated = false;
+
+  for (const auto& entry : counts) {
+    const auto& requestor = entry.first;
+    const auto& counters = entry.second;
+
+    if (d_queryRateRule.warningRateExceeded(counters.queries, now)) {
+      handleWarning(blocks, now, requestor, d_queryRateRule, updated);
+    }
+
+    if (d_queryRateRule.rateExceeded(counters.queries, now)) {
+      addBlock(blocks, now, requestor, d_queryRateRule, updated);
+      continue;
+    }
+
+    if (d_respRateRule.warningRateExceeded(counters.respBytes, now)) {
+      handleWarning(blocks, now, requestor, d_respRateRule, updated);
+    }
+
+    if (d_respRateRule.rateExceeded(counters.respBytes, now)) {
+      addBlock(blocks, now, requestor, d_respRateRule, updated);
+      continue;
+    }
+
+    for (const auto& pair : d_qtypeRules) {
+      const auto qtype = pair.first;
+
+      const auto& typeIt = counters.d_qtypeCounts.find(qtype);
+      if (typeIt != counters.d_qtypeCounts.cend()) {
+
+        if (pair.second.warningRateExceeded(typeIt->second, now)) {
+          handleWarning(blocks, now, requestor, pair.second, updated);
+        }
+
+        if (pair.second.rateExceeded(typeIt->second, now)) {
+          addBlock(blocks, now, requestor, pair.second, updated);
+          break;
+        }
+      }
+    }
+
+    for (const auto& pair : d_rcodeRules) {
+      const auto rcode = pair.first;
+
+      const auto& rcodeIt = counters.d_rcodeCounts.find(rcode);
+      if (rcodeIt != counters.d_rcodeCounts.cend()) {
+        if (pair.second.warningRateExceeded(rcodeIt->second, now)) {
+          handleWarning(blocks, now, requestor, pair.second, updated);
+        }
+
+        if (pair.second.rateExceeded(rcodeIt->second, now)) {
+          addBlock(blocks, now, requestor, pair.second, updated);
+          break;
+        }
+      }
+    }
+
+    for (const auto& pair : d_rcodeRatioRules) {
+      const auto rcode = pair.first;
+
+      const auto& rcodeIt = counters.d_rcodeCounts.find(rcode);
+      if (rcodeIt != counters.d_rcodeCounts.cend()) {
+        if (pair.second.warningRatioExceeded(counters.queries, rcodeIt->second)) {
+          handleWarning(blocks, now, requestor, pair.second, updated);
+        }
+
+        if (pair.second.ratioExceeded(counters.queries, rcodeIt->second)) {
+          addBlock(blocks, now, requestor, pair.second, updated);
+          break;
+        }
+      }
+    }
+  }
+
+  if (updated && blocks) {
+    g_dynblockNMG.setState(std::move(*blocks));
+  }
+
+  if (!statNodeRoot.empty()) {
+    StatNode::Stat node;
+    std::unordered_set<DNSName> namesToBlock;
+    statNodeRoot.visit([this,&namesToBlock](const StatNode* node_, const StatNode::Stat& self, const StatNode::Stat& children) {
+                         bool block = false;
+
+                         if (d_smtVisitorFFI) {
+                           dnsdist_ffi_stat_node_t tmp(*node_, self, children);
+                           block = d_smtVisitorFFI(&tmp);
+                         }
+                         else {
+                           block = d_smtVisitor(*node_, self, children);
+                         }
+
+                         if (block) {
+                           namesToBlock.insert(DNSName(node_->fullname));
+                         }
+                       },
+      node);
+
+    if (!namesToBlock.empty()) {
+      updated = false;
+      SuffixMatchTree<DynBlock> smtBlocks = g_dynblockSMT.getCopy();
+      for (const auto& name : namesToBlock) {
+        addOrRefreshBlockSMT(smtBlocks, now, name, d_suffixMatchRule, updated);
+      }
+      if (updated) {
+        g_dynblockSMT.setState(std::move(smtBlocks));
+      }
+    }
+  }
+}
+
+bool DynBlockRulesGroup::checkIfQueryTypeMatches(const Rings::Query& query)
+{
+  auto rule = d_qtypeRules.find(query.qtype);
+  if (rule == d_qtypeRules.end()) {
+    return false;
+  }
+
+  return rule->second.matches(query.when);
+}
+
+bool DynBlockRulesGroup::checkIfResponseCodeMatches(const Rings::Response& response)
+{
+  auto rule = d_rcodeRules.find(response.dh.rcode);
+  if (rule != d_rcodeRules.end() && rule->second.matches(response.when)) {
+    return true;
+  }
+
+  auto ratio = d_rcodeRatioRules.find(response.dh.rcode);
+  if (ratio != d_rcodeRatioRules.end() && ratio->second.matches(response.when)) {
+    return true;
+  }
+
+  return false;
+}
+
+void DynBlockRulesGroup::addOrRefreshBlock(boost::optional<NetmaskTree<DynBlock> >& blocks, const struct timespec& now, const ComboAddress& requestor, const DynBlockRule& rule, bool& updated, bool warning)
+{
+  if (d_excludedSubnets.match(requestor)) {
+    /* do not add a block for excluded subnets */
+    return;
+  }
+
+  if (!blocks) {
+    blocks = g_dynblockNMG.getCopy();
+  }
+  struct timespec until = now;
+  until.tv_sec += rule.d_blockDuration;
+  unsigned int count = 0;
+  const auto& got = blocks->lookup(Netmask(requestor));
+  bool expired = false;
+  bool wasWarning = false;
+
+  if (got) {
+    if (warning && !got->second.warning) {
+      /* we have an existing entry which is not a warning,
+         don't override it */
+      return;
+    }
+    else if (!warning && got->second.warning) {
+      wasWarning = true;
+    }
+    else {
+      if (until < got->second.until) {
+        // had a longer policy
+        return;
+      }
+    }
+
+    if (now < got->second.until) {
+      // only inherit count on fresh query we are extending
+      count = got->second.blocks;
+    }
+    else {
+      expired = true;
+    }
+  }
+
+  DynBlock db{rule.d_blockReason, until, DNSName(), warning ? DNSAction::Action::NoOp : rule.d_action};
+  db.blocks = count;
+  db.warning = warning;
+  if (!d_beQuiet && (!got || expired || wasWarning)) {
+    warnlog("Inserting %sdynamic block for %s for %d seconds: %s", warning ? "(warning) " :"", requestor.toString(), rule.d_blockDuration, rule.d_blockReason);
+  }
+  blocks->insert(Netmask(requestor)).second = db;
+  updated = true;
+}
+
+void DynBlockRulesGroup::addOrRefreshBlockSMT(SuffixMatchTree<DynBlock>& blocks, const struct timespec& now, const DNSName& name, const DynBlockRule& rule, bool& updated)
+{
+  if (d_excludedDomains.check(name)) {
+    /* do not add a block for excluded domains */
+    return;
+  }
+
+  struct timespec until = now;
+  until.tv_sec += rule.d_blockDuration;
+  unsigned int count = 0;
+  const auto& got = blocks.lookup(name);
+  bool expired = false;
+  DNSName domain(name.makeLowerCase());
+
+  if (got) {
+    if (until < got->until) {
+      // had a longer policy
+      return;
+    }
+
+    if (now < got->until) {
+      // only inherit count on fresh query we are extending
+      count = got->blocks;
+    }
+    else {
+      expired = true;
+    }
+  }
+
+  DynBlock db{rule.d_blockReason, until, domain, rule.d_action};
+  db.blocks = count;
+
+  if (!d_beQuiet && (!got || expired)) {
+    warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, rule.d_blockDuration, rule.d_blockReason);
+  }
+  blocks.add(domain, db);
+  updated = true;
+}
+
+void DynBlockRulesGroup::processQueryRules(counts_t& counts, const struct timespec& now)
+{
+  if (!hasQueryRules()) {
+    return;
+  }
+
+  d_queryRateRule.d_cutOff = d_queryRateRule.d_minTime = now;
+  d_queryRateRule.d_cutOff.tv_sec -= d_queryRateRule.d_seconds;
+
+  for (auto& rule : d_qtypeRules) {
+    rule.second.d_cutOff = rule.second.d_minTime = now;
+    rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
+  }
+
+  for (const auto& shard : g_rings.d_shards) {
+    std::lock_guard<std::mutex> rl(shard->queryLock);
+    for(const auto& c : shard->queryRing) {
+      if (now < c.when) {
+        continue;
+      }
+
+      bool qRateMatches = d_queryRateRule.matches(c.when);
+      bool typeRuleMatches = checkIfQueryTypeMatches(c);
+
+      if (qRateMatches || typeRuleMatches) {
+        auto& entry = counts[c.requestor];
+        if (qRateMatches) {
+          ++entry.queries;
+        }
+        if (typeRuleMatches) {
+          ++entry.d_qtypeCounts[c.qtype];
+        }
+      }
+    }
+  }
+}
+
+void DynBlockRulesGroup::processResponseRules(counts_t& counts, StatNode& root, const struct timespec& now)
+{
+  if (!hasResponseRules() && !hasSuffixMatchRules()) {
+    return;
+  }
+
+  d_respRateRule.d_cutOff = d_respRateRule.d_minTime = now;
+  d_respRateRule.d_cutOff.tv_sec -= d_respRateRule.d_seconds;
+
+  d_suffixMatchRule.d_cutOff = d_suffixMatchRule.d_minTime = now;
+  d_suffixMatchRule.d_cutOff.tv_sec -= d_suffixMatchRule.d_seconds;
+
+  for (auto& rule : d_rcodeRules) {
+    rule.second.d_cutOff = rule.second.d_minTime = now;
+    rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
+  }
+
+  for (auto& rule : d_rcodeRatioRules) {
+    rule.second.d_cutOff = rule.second.d_minTime = now;
+    rule.second.d_cutOff.tv_sec -= rule.second.d_seconds;
+  }
+
+  for (const auto& shard : g_rings.d_shards) {
+    std::lock_guard<std::mutex> rl(shard->respLock);
+    for(const auto& c : shard->respRing) {
+      if (now < c.when) {
+        continue;
+      }
+
+      auto& entry = counts[c.requestor];
+      ++entry.queries;
+      bool respRateMatches = d_respRateRule.matches(c.when);
+      bool suffixMatchRuleMatches = d_suffixMatchRule.matches(c.when);
+      bool rcodeRuleMatches = checkIfResponseCodeMatches(c);
+
+      if (respRateMatches || rcodeRuleMatches) {
+        if (respRateMatches) {
+          entry.respBytes += c.size;
+        }
+        if (rcodeRuleMatches) {
+          ++entry.d_rcodeCounts[c.dh.rcode];
+        }
+      }
+
+      if (suffixMatchRuleMatches) {
+        root.submit(c.name, ((c.dh.rcode == 0 && c.usec == std::numeric_limits<unsigned int>::max()) ? -1 : c.dh.rcode), c.size, boost::none);
+      }
+    }
+  }
+}
diff --git a/pdns/dnsdistdist/dnsdist-healthchecks.cc b/pdns/dnsdistdist/dnsdist-healthchecks.cc
new file mode 100644 (file)
index 0000000..3dc7e6a
--- /dev/null
@@ -0,0 +1,289 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dnsdist-healthchecks.hh"
+#include "dnswriter.hh"
+#include "dolog.hh"
+
+bool g_verboseHealthChecks{false};
+
+void updateHealthCheckResult(const std::shared_ptr<DownstreamState>& dss, bool newState)
+{
+  if (newState) {
+    /* check succeeded */
+    dss->currentCheckFailures = 0;
+
+    if (!dss->upStatus) {
+      /* we were marked as down */
+      dss->consecutiveSuccessfulChecks++;
+      if (dss->consecutiveSuccessfulChecks < dss->minRiseSuccesses) {
+        /* if we need more than one successful check to rise
+           and we didn't reach the threshold yet,
+           let's stay down */
+        newState = false;
+      }
+    }
+  }
+  else {
+    /* check failed */
+    dss->consecutiveSuccessfulChecks = 0;
+
+    if (dss->upStatus) {
+      /* we are currently up */
+      dss->currentCheckFailures++;
+      if (dss->currentCheckFailures < dss->maxCheckFailures) {
+        /* we need more than one failure to be marked as down,
+           and we did not reach the threshold yet, let's stay down */
+        newState = true;
+      }
+    }
+  }
+  if(newState != dss->upStatus) {
+    warnlog("Marking downstream %s as '%s'", dss->getNameWithAddr(), newState ? "up" : "down");
+
+    if (newState && !dss->connected) {
+      newState = dss->reconnect();
+
+      if (dss->connected && !dss->threadStarted.test_and_set()) {
+        dss->tid = std::thread(responderThread, dss);
+      }
+    }
+
+    dss->upStatus = newState;
+    dss->currentCheckFailures = 0;
+    dss->consecutiveSuccessfulChecks = 0;
+    if (g_snmpAgent && g_snmpTrapsEnabled) {
+      g_snmpAgent->sendBackendStatusChangeTrap(dss);
+    }
+  }
+}
+
+static bool handleResponse(std::shared_ptr<HealthCheckData>& data)
+{
+  auto& ds = data->d_ds;
+  try {
+    string reply;
+    ComboAddress from;
+    data->d_sock.recvFrom(reply, from);
+
+    /* we are using a connected socket but hey.. */
+    if (from != ds->remote) {
+      if (g_verboseHealthChecks) {
+        infolog("Invalid health check response received from %s, expecting one from %s", from.toStringWithPort(), ds->remote.toStringWithPort());
+      }
+      return false;
+    }
+
+    const dnsheader * responseHeader = reinterpret_cast<const dnsheader *>(reply.c_str());
+
+    if (reply.size() < sizeof(*responseHeader)) {
+      if (g_verboseHealthChecks) {
+        infolog("Invalid health check response of size %d from backend %s, expecting at least %d", reply.size(), ds->getNameWithAddr(), sizeof(*responseHeader));
+      }
+      return false;
+    }
+
+    if (responseHeader->id != data->d_queryID) {
+      if (g_verboseHealthChecks) {
+        infolog("Invalid health check response id %d from backend %s, expecting %d", data->d_queryID, ds->getNameWithAddr(), data->d_queryID);
+      }
+      return false;
+    }
+
+    if (!responseHeader->qr) {
+      if (g_verboseHealthChecks) {
+        infolog("Invalid health check response from backend %s, expecting QR to be set", ds->getNameWithAddr());
+      }
+      return false;
+    }
+
+    if (responseHeader->rcode == RCode::ServFail) {
+      if (g_verboseHealthChecks) {
+        infolog("Backend %s responded to health check with ServFail", ds->getNameWithAddr());
+      }
+      return false;
+    }
+
+    if (ds->mustResolve && (responseHeader->rcode == RCode::NXDomain || responseHeader->rcode == RCode::Refused)) {
+      if (g_verboseHealthChecks) {
+        infolog("Backend %s responded to health check with %s while mustResolve is set", ds->getNameWithAddr(), responseHeader->rcode == RCode::NXDomain ? "NXDomain" : "Refused");
+      }
+      return false;
+    }
+
+    uint16_t receivedType;
+    uint16_t receivedClass;
+    DNSName receivedName(reply.c_str(), reply.size(), sizeof(dnsheader), false, &receivedType, &receivedClass);
+
+    if (receivedName != data->d_checkName || receivedType != data->d_checkType || receivedClass != data->d_checkClass) {
+      if (g_verboseHealthChecks) {
+        infolog("Backend %s responded to health check with an invalid qname (%s vs %s), qtype (%s vs %s) or qclass (%d vs %d)", ds->getNameWithAddr(), receivedName.toLogString(), data->d_checkName.toLogString(), QType(receivedType).getName(), QType(data->d_checkType).getName(), receivedClass, data->d_checkClass);
+      }
+      return false;
+    }
+  }
+  catch(const std::exception& e)
+  {
+    if (g_verboseHealthChecks) {
+      infolog("Error checking the health of backend %s: %s", ds->getNameWithAddr(), e.what());
+    }
+    return false;
+  }
+  catch(...)
+  {
+    if (g_verboseHealthChecks) {
+      infolog("Unknown exception while checking the health of backend %s", ds->getNameWithAddr());
+    }
+    return false;
+  }
+
+  return true;
+}
+
+static void healthCheckCallback(int fd, FDMultiplexer::funcparam_t& param)
+{
+  auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
+  data->d_mplexer->removeReadFD(fd);
+  updateHealthCheckResult(data->d_ds, handleResponse(data));
+}
+
+static void initialHealthCheckCallback(int fd, FDMultiplexer::funcparam_t& param)
+{
+  auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(param);
+  data->d_mplexer->removeReadFD(fd);
+  bool up = handleResponse(data);
+  warnlog("Marking downstream %s as '%s'", data->d_ds->getNameWithAddr(), up ? "up" : "down");
+  data->d_ds->upStatus = up;
+}
+
+bool queueHealthCheck(std::shared_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, bool initialCheck)
+{
+  try
+  {
+    uint16_t queryID = getRandomDNSID();
+    DNSName checkName = ds->checkName;
+    uint16_t checkType = ds->checkType.getCode();
+    uint16_t checkClass = ds->checkClass;
+    dnsheader checkHeader;
+    memset(&checkHeader, 0, sizeof(checkHeader));
+
+    checkHeader.qdcount = htons(1);
+    checkHeader.id = queryID;
+
+    checkHeader.rd = true;
+    if (ds->setCD) {
+      checkHeader.cd = true;
+    }
+
+    if (ds->checkFunction) {
+      std::lock_guard<std::mutex> lock(g_luamutex);
+      auto ret = ds->checkFunction(checkName, checkType, checkClass, &checkHeader);
+      checkName = std::get<0>(ret);
+      checkType = std::get<1>(ret);
+      checkClass = std::get<2>(ret);
+    }
+
+    vector<uint8_t> packet;
+    DNSPacketWriter dpw(packet, checkName, checkType, checkClass);
+    dnsheader * requestHeader = dpw.getHeader();
+    *requestHeader = checkHeader;
+
+    Socket sock(ds->remote.sin4.sin_family, SOCK_DGRAM);
+    sock.setNonBlocking();
+    if (!IsAnyAddress(ds->sourceAddr)) {
+      sock.setReuseAddr();
+      if (!ds->sourceItfName.empty()) {
+#ifdef SO_BINDTODEVICE
+        int res = setsockopt(sock.getHandle(), SOL_SOCKET, SO_BINDTODEVICE, ds->sourceItfName.c_str(), ds->sourceItfName.length());
+        if (res != 0 && g_verboseHealthChecks) {
+          infolog("Error setting SO_BINDTODEVICE on the health check socket for backend '%s': %s", ds->getNameWithAddr(), stringerror());
+        }
+#endif
+      }
+      sock.bind(ds->sourceAddr);
+    }
+    sock.connect(ds->remote);
+    ssize_t sent = udpClientSendRequestToBackend(ds, sock.getHandle(), reinterpret_cast<char*>(&packet[0]), packet.size(), true);
+    if (sent < 0) {
+      int ret = errno;
+      if (g_verboseHealthChecks)
+        infolog("Error while sending a health check query to backend %s: %d", ds->getNameWithAddr(), ret);
+      return false;
+    }
+
+    auto data = std::make_shared<HealthCheckData>(mplexer, ds, std::move(sock), std::move(checkName), checkType, checkClass, queryID);
+    struct timeval ttd;
+    gettimeofday(&ttd, nullptr);
+    ttd.tv_sec += ds->checkTimeout / 1000; /* ms to seconds */
+    ttd.tv_usec += (ds->checkTimeout % 1000) * 1000; /* remaining ms to us */
+    if (ttd.tv_usec > 1000000) {
+      ++ttd.tv_sec;
+      ttd.tv_usec -= 1000000;
+    }
+    mplexer->addReadFD(data->d_sock.getHandle(), initialCheck ? &initialHealthCheckCallback : &healthCheckCallback, data, &ttd);
+
+    return true;
+  }
+  catch(const std::exception& e)
+  {
+    if (g_verboseHealthChecks) {
+      infolog("Error checking the health of backend %s: %s", ds->getNameWithAddr(), e.what());
+    }
+    return false;
+  }
+  catch(...)
+  {
+    if (g_verboseHealthChecks) {
+      infolog("Unknown exception while checking the health of backend %s", ds->getNameWithAddr());
+    }
+    return false;
+  }
+}
+
+void handleQueuedHealthChecks(std::shared_ptr<FDMultiplexer>& mplexer, bool initial)
+{
+  while (mplexer->getWatchedFDCount(false) > 0) {
+    struct timeval now;
+    int ret = mplexer->run(&now, 100);
+    if (ret == -1) {
+      if (g_verboseHealthChecks) {
+        infolog("Error while waiting for the health check response from backends: %d", ret);
+      }
+      break;
+    }
+    auto timeouts = mplexer->getTimeouts(now);
+    for (const auto& timeout : timeouts) {
+      mplexer->removeReadFD(timeout.first);
+      auto data = boost::any_cast<std::shared_ptr<HealthCheckData>>(timeout.second);
+      if (g_verboseHealthChecks) {
+        infolog("Timeout while waiting for the health check response from backend %s", data->d_ds->getNameWithAddr());
+      }
+      if (initial) {
+        warnlog("Marking downstream %s as 'down'", data->d_ds->getNameWithAddr());
+        data->d_ds->upStatus = false;
+      }
+      else {
+        updateHealthCheckResult(data->d_ds, false);
+      }
+    }
+  }
+}
diff --git a/pdns/dnsdistdist/dnsdist-healthchecks.hh b/pdns/dnsdistdist/dnsdist-healthchecks.hh
new file mode 100644 (file)
index 0000000..99d512b
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include "dnsdist.hh"
+#include "mplexer.hh"
+#include "sstuff.hh"
+
+struct HealthCheckData
+{
+  HealthCheckData(std::shared_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, Socket&& sock, DNSName&& checkName, uint16_t checkType, uint16_t checkClass, uint16_t queryID): d_mplexer(mplexer), d_ds(ds), d_sock(std::move(sock)), d_checkName(std::move(checkName)), d_checkType(checkType), d_checkClass(checkClass), d_queryID(queryID)
+  {
+  }
+
+  std::shared_ptr<FDMultiplexer> d_mplexer;
+  const std::shared_ptr<DownstreamState> d_ds;
+  Socket d_sock;
+  DNSName d_checkName;
+  uint16_t d_checkType;
+  uint16_t d_checkClass;
+  uint16_t d_queryID;
+};
+
+extern bool g_verboseHealthChecks;
+
+void updateHealthCheckResult(const std::shared_ptr<DownstreamState>& dss, bool newState);
+bool queueHealthCheck(std::shared_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, bool initial=false);
+void handleQueuedHealthChecks(std::shared_ptr<FDMultiplexer>& mplexer, bool initial=false);
+
index 876c8124ddf715a5e0e8c987c8be2ed11b1ce842..e8d6350eb9629aad3f68b23b6b311aecea73e4cd 100644 (file)
@@ -32,7 +32,7 @@
 #include "ipcipher.hh"
 #endif /* HAVE_LIBCRYPTO */
 
-void setupLuaBindingsProtoBuf(bool client)
+void setupLuaBindingsProtoBuf(bool client, bool configCheck)
 {
 #ifdef HAVE_LIBCRYPTO
   g_lua.registerFunction<ComboAddress(ComboAddress::*)(const std::string& key)>("ipencrypt", [](const ComboAddress& ca, const std::string& key) {
@@ -72,17 +72,29 @@ void setupLuaBindingsProtoBuf(bool client)
   g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(time_t, uint32_t)>("setQueryTime", [](DNSDistProtoBufMessage& message, time_t sec, uint32_t usec) { message.setQueryTime(sec, usec); });
   g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(uint8_t)>("setResponseCode", [](DNSDistProtoBufMessage& message, uint8_t rcode) { message.setResponseCode(rcode); });
   g_lua.registerFunction<std::string(DNSDistProtoBufMessage::*)()>("toDebugString", [](const DNSDistProtoBufMessage& message) { return message.toDebugString(); });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&)>("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) {
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&, boost::optional<uint16_t>)>("setRequestor", [](DNSDistProtoBufMessage& message, const ComboAddress& addr, boost::optional<uint16_t> port) {
       message.setRequestor(addr);
+      if (port) {
+        message.setRequestorPort(*port);
+      }
     });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str) {
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&, boost::optional<uint16_t>)>("setRequestorFromString", [](DNSDistProtoBufMessage& message, const std::string& str, boost::optional<uint16_t> port) {
       message.setRequestor(str);
+      if (port) {
+        message.setRequestorPort(*port);
+      }
     });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&)>("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr) {
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const ComboAddress&, boost::optional<uint16_t>)>("setResponder", [](DNSDistProtoBufMessage& message, const ComboAddress& addr, boost::optional<uint16_t> port) {
       message.setResponder(addr);
+      if (port) {
+        message.setResponderPort(*port);
+      }
     });
-  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str) {
+  g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&, boost::optional<uint16_t>)>("setResponderFromString", [](DNSDistProtoBufMessage& message, const std::string& str, boost::optional<uint16_t> port) {
       message.setResponder(str);
+      if (port) {
+        message.setResponderPort(*port);
+      }
     });
   g_lua.registerFunction<void(DNSDistProtoBufMessage::*)(const std::string&)>("setServerIdentity", [](DNSDistProtoBufMessage& message, const std::string& str) {
       message.setServerIdentity(str);
@@ -94,16 +106,16 @@ void setupLuaBindingsProtoBuf(bool client)
     });
 
   /* RemoteLogger */
-  g_lua.writeFunction("newRemoteLogger", [client](const std::string& remote, boost::optional<uint16_t> timeout, boost::optional<uint64_t> maxQueuedEntries, boost::optional<uint8_t> reconnectWaitTime) {
-      if (client) {
+  g_lua.writeFunction("newRemoteLogger", [client,configCheck](const std::string& remote, boost::optional<uint16_t> timeout, boost::optional<uint64_t> maxQueuedEntries, boost::optional<uint8_t> reconnectWaitTime) {
+      if (client || configCheck) {
         return std::shared_ptr<RemoteLoggerInterface>(nullptr);
       }
       return std::shared_ptr<RemoteLoggerInterface>(new RemoteLogger(ComboAddress(remote), timeout ? *timeout : 2, maxQueuedEntries ? (*maxQueuedEntries*100) : 10000, reconnectWaitTime ? *reconnectWaitTime : 1, client));
     });
 
-  g_lua.writeFunction("newFrameStreamUnixLogger", [client](const std::string& address) {
+  g_lua.writeFunction("newFrameStreamUnixLogger", [client,configCheck](const std::string& address) {
 #ifdef HAVE_FSTRM
-      if (client) {
+      if (client || configCheck) {
         return std::shared_ptr<RemoteLoggerInterface>(nullptr);
       }
       return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_UNIX, address, !client));
@@ -112,9 +124,9 @@ void setupLuaBindingsProtoBuf(bool client)
 #endif /* HAVE_FSTRM */
     });
 
-  g_lua.writeFunction("newFrameStreamTcpLogger", [client](const std::string& address) {
+  g_lua.writeFunction("newFrameStreamTcpLogger", [client,configCheck](const std::string& address) {
 #if defined(HAVE_FSTRM) && defined(HAVE_FSTRM_TCP_WRITER_INIT)
-      if (client) {
+      if (client || configCheck) {
         return std::shared_ptr<RemoteLoggerInterface>(nullptr);
       }
       return std::shared_ptr<RemoteLoggerInterface>(new FrameStreamLogger(AF_INET, address, !client));
index 04cb57a0dd9b5e197a500b528bf70578e3e689e3..a478c8eacee057b4d3073f665360454048d705ea 100644 (file)
@@ -55,7 +55,7 @@ The following example sets the CD flag to true and change the QName to "powerdns
       return newDNSName("powerdns.com."), dnsdist.AAAA, qclass
     end
 
-    newServer("2620:0:0ccd::2")
+    newServer({address="2620:0:0ccd::2", checkFunction=myHealthCheck})
 
 Source address selection
 ------------------------
index 919ebedc964aeb06ddf3b2823f70a8e00fcbf84e..f86dc6d8523fb2373f5bfd34434950dd6f74acbe 100644 (file)
@@ -55,8 +55,9 @@ This is a side-effect of the internal implementation of the consistent hashing a
 
 You can also set the hash perturbation value, see :func:`setWHashedPertubation`. To achieve consistent distribution over :program:`dnsdist` restarts, you will also need to explicitly set the backend's UUIDs with the ``id`` option of :func:`newServer`. You can get the current UUIDs of your backends by calling :func:`showServers` with the ``showUUIDs=true`` option.
 
-Since 1.5.0, a bounded-load version is also supported, preventing one server from receiving much more queries than the others, even if the distribution of queries is not perfect. This "consistent hashing with bounded loads" algorithm is enabled by setting func:`setConsistentHashingBalancingFactor` to a value other than 0, which is the default. This value is the maximum number of outstanding queries that a given server can have at a given time, as a ratio of the average number of outstanding queries for all the active servers in the pool.
-For example, setting func:`setConsistentHashingBalancingFactor` to 1.5 means that no server will be allowed to have more outstanding queries than 1.5 times the average of all outstanding queries in the pool. The algorithm will try to select a server based on the hash of the qname, as is done when no bounded-load is set, but will disqualify all servers that have more outstanding queries than the average times the factor, until a suitable server is found.
+Since 1.5.0, a bounded-load version is also supported, preventing one server from receiving much more queries than the others, even if the distribution of queries is not perfect. This "consistent hashing with bounded loads" algorithm is enabled by setting :func:`setConsistentHashingBalancingFactor` to a value other than 0, which is the default. This value is the maximum number of outstanding queries that a given server can have at a given time, as a ratio of the average number of outstanding queries for all the active servers in the pool.
+
+For example, setting :func:`setConsistentHashingBalancingFactor` to 1.5 means that no server will be allowed to have more outstanding queries than 1.5 times the average of all outstanding queries in the pool. The algorithm will try to select a server based on the hash of the qname, as is done when no bounded-load is set, but will disqualify all servers that have more outstanding queries than the average times the factor, until a suitable server is found.
 The higher the factor, the more imbalance between the servers is allowed.
 
 ``roundrobin``
index 7d40c1b552e876655440dda7411fe1e9c59ff849..9aa47383f8fecc464b9e16ffa6098a35cba6e4cd 100644 (file)
@@ -227,6 +227,12 @@ Control Socket, Console and Webserver
 
   Returns true while the console client is parsing the configuration.
 
+.. function:: inConfigCheck()
+
+  .. versionadded:: 1.5.0
+
+  Returns true while the configuration is being checked, ie when run with ``--check-config``.
+
 .. function:: makeKey()
 
   Generate and print an encryption key.
@@ -428,9 +434,12 @@ Servers
 
 .. function:: getServer(index) -> Server
 
+  .. versionchanged:: 1.5.0
+    ``index`` might be an UUID.
+
   Get a :class:`Server`
 
-  :param int index: The number of the server (as seen in :func:`showServers`).
+  :param int or str index: The number of the server (as seen in :func:`showServers`) or its UUID as a string.
   :returns:  The :class:`Server` object or nil
 
 .. function:: getServers()
@@ -438,11 +447,15 @@ Servers
   Returns a table with all defined servers.
 
 .. function:: rmServer(index)
+              rmServer(uuid)
               rmServer(server)
 
+  .. versionchanged:: 1.5.0
+    ``uuid`` selection added.
+
   Remove a backend server.
 
-  :param int index: The number of the server (as seen in :func:`showServers`).
+  :param int or str index: The number of the server (as seen in :func:`showServers`), its UUID as a string, or a server object.
   :param Server server: A :class:`Server` object as returned by e.g. :func:`getServer`.
 
 Server Functions
@@ -1020,6 +1033,21 @@ faster than the existing rules.
     :param int action: The action to take when the dynamic block matches, see :ref:`here <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
     :param int warningRate: If set to a non-zero value, the rate above which a warning message will be issued and a no-op block inserted
 
+  .. method:: DynBlockRulesGroup:setRCodeRatio(rcode, ratio, seconds, reason, blockingTime, minimumNumberOfResponses [, action [, warningRate]])
+
+    .. versionadded:: 1.5.0
+
+    Adds a rate-limiting rule for the ratio of responses of code ``rcode`` over the total number of responses for a given client.
+
+    :param int rcode: The response code
+    :param int ratio: Ratio of responses per second of the given rcode over the total number of responses for this client to exceed
+    :param int seconds: Number of seconds the ratio has been exceeded
+    :param string reason: The message to show next to the blocks
+    :param int blockingTime: The number of seconds this block to expire
+    :param int minimumNumberOfResponses: How many total responses is required for this rule to apply
+    :param int action: The action to take when the dynamic block matches, see :ref:`here <DNSAction>`. (default to the one set with :func:`setDynBlocksAction`)
+    :param int warningRatio: If set to a non-zero value, the ratio above which a warning message will be issued and a no-op block inserted
+
   .. method:: DynBlockRulesGroup:setQTypeRate(qtype, rate, seconds, reason, blockingTime [, action [, warningRate]])
 
     .. versionchanged:: 1.3.3
index 1cb71a7d667724e170845a591e5c723b670d2eff..cbceafd60b39ce5e900f72195a2475126ea212c6 100644 (file)
@@ -62,29 +62,45 @@ Protobuf Logging Reference
     :param int sec: Optional query time in seconds.
     :param int usec: Optional query time in additional micro-seconds.
 
-  .. method:: DNSDistProtoBufMessage:setRequestor(address)
+  .. method:: DNSDistProtoBufMessage:setRequestor(address [, port])
+
+    .. versionchanged:: 1.5.0
+      ``port`` optional parameter added.
 
     Set the requestor's address.
 
     :param ComboAddress address: The address to set to
+    :param int port: The requestor source port
+
+  .. method:: DNSDistProtoBufMessage:setRequestorFromString(address [, port])
 
-  .. method:: DNSDistProtoBufMessage:setRequestorFromString(address)
+    .. versionchanged:: 1.5.0
+      ``port`` optional parameter added.
 
     Set the requestor's address from a string.
 
     :param string address: The address to set to
+    :param int port: The requestor source port
 
-  .. method:: DNSDistProtoBufMessage:setResponder(address)
+  .. method:: DNSDistProtoBufMessage:setResponder(address [, port])
+
+    .. versionchanged:: 1.5.0
+      ``port`` optional parameter added.
 
     Set the responder's address.
 
     :param ComboAddress address: The address to set to
+    :param int port: The responder port
+
+  .. method:: DNSDistProtoBufMessage:setResponderFromString(address [, port])
 
-  .. method:: DNSDistProtoBufMessage:setResponderFromString(address)
+    .. versionchanged:: 1.5.0
+      ``port`` optional parameter added.
 
     Set the responder's address.
 
     :param string address: The address to set to
+    :param int port: The responder port
 
   .. method:: DNSDistProtoBufMessage:setResponseCode(rcode)
 
index be5c7ac41b11cd268df1774f193506a6ffc159b7..1d698b69d5fc59466b646ebcc9fd8d1c0abc5658 100644 (file)
@@ -617,6 +617,7 @@ These ``DNSRule``\ s be one of the following items:
   :param KeyValueLookupKey lookupKey: The key to use for the lookup
 
 .. function:: MaxQPSIPRule(qps[, v4Mask[, v6Mask[, burst[, expiration[, cleanupDelay[, scanFraction]]]]]])
+
   .. versionchanged:: 1.3.1
     Added the optional parameters ``expiration``, ``cleanupDelay`` and ``scanFraction``.
 
@@ -959,24 +960,44 @@ The following actions exist.
   :param int v6: The IPv6 netmask length
 
 
-.. function:: ERCodeAction(rcode)
+.. function:: ERCodeAction(rcode [, options])
 
   .. versionadded:: 1.4.0
 
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
+
   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.
+  :param table options: A table with key: value pairs with options.
 
-.. function:: HTTPStatusAction(status, body, contentType="")
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+
+.. function:: HTTPStatusAction(status, body, contentType="" [, options])
 
   .. versionadded:: 1.4.0
 
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
+
   Return an HTTP response with a status code of ''status''. For HTTP redirects, ''body'' should be the redirect URL.
 
   :param int status: The HTTP status code to return.
   :param string body: The body of the HTTP response, or a URL if the status code is a redirect (3xx).
   :param string contentType: The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ''application/dns-message''.
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
 
 .. function:: KeyValueStoreLookupAction(kvs, lookupKey, destinationTag)
 
@@ -1091,12 +1112,22 @@ The following actions exist.
   :param int maxqps: The QPS limit for that pool
   :param string poolname: The name of the pool
 
-.. function:: RCodeAction(rcode)
+.. function:: RCodeAction(rcode [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
 
   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.
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
 
 .. function:: RemoteLogAction(remoteLogger[, alterFunction [, options]])
 
@@ -1174,20 +1205,40 @@ The following actions exist.
 
   :param string message: The message to include
 
-.. function:: SpoofAction(ip[, ip[...]])
-              SpoofAction(ips)
+.. function:: SpoofAction(ip [, options])
+              SpoofAction(ips [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
 
   Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses.
   If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in.
 
   :param string ip: An IPv4 and/or IPv6 address to spoof
   :param {string} ips: A table of IPv4 and/or IPv6 addresses to spoof
+  :param table options: A table with key: value pairs with options.
+
+  Options:
 
-.. function:: SpoofCNAMEAction(cname)
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+
+.. function:: SpoofCNAMEAction(cname [, options])
+
+  .. versionchanged:: 1.5.0
+    Added the optional parameter ``options``.
 
   Forge a response with the specified CNAME value.
 
   :param string cname: The name to respond with
+  :param table options: A table with key: value pairs with options.
+
+  Options:
+
+  * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+  * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
 
 .. function:: TagAction(name, value)
 
index ee9b9ef0fc2a98fabc5de114559bc7366779dde1..5da395c37c4064f9f0aaba8a47482ae86f22d104 100644 (file)
@@ -19,7 +19,7 @@
 #include <sodium.h>
 #endif /* HAVE_LIBSODIUM */
 
-#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER)
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2090100fL)
 /* OpenSSL < 1.1.0 needs support for threading/locking in the calling application. */
 static pthread_mutex_t *openssllocks{nullptr};
 
@@ -62,7 +62,7 @@ static void openssl_thread_cleanup()
   OPENSSL_free(openssllocks);
 }
 
-#endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) */
+#endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2090100fL) */
 
 static std::atomic<uint64_t> s_users;
 static int s_ticketsKeyIndex{-1};
@@ -72,7 +72,13 @@ static int s_keyLogIndex{-1};
 void registerOpenSSLUser()
 {
   if (s_users.fetch_add(1) == 0) {
-#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER)
+#if (OPENSSL_VERSION_NUMBER >= 0x1010000fL && (!defined LIBRESSL_VERSION_NUMBER || LIBRESSL_VERSION_NUMBER >= 0x2070000fL))
+    /* load the default configuration file (or one specified via OPENSSL_CONF),
+       which can then be used to load engines */
+    OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, nullptr);
+#endif
+
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER && LIBRESSL_VERSION_NUMBER < 0x2090100fL))
     SSL_load_error_strings();
     OpenSSL_add_ssl_algorithms();
     openssl_thread_setup();
@@ -100,7 +106,7 @@ void registerOpenSSLUser()
 void unregisterOpenSSLUser()
 {
   if (s_users.fetch_sub(1) == 1) {
-#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER)
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER && LIBRESSL_VERSION_NUMBER < 0x2090100fL))
     ERR_free_strings();
 
     EVP_cleanup();
index f62e8a223b5a8e29236af20fef7a66f8608f853f..3216d4692cb55c2650bd15d11c92565c94044416 100644 (file)
@@ -259,6 +259,123 @@ BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRate) {
 
 }
 
+BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_RCodeRatio) {
+  dnsheader dh;
+  DNSName qname("rings.powerdns.com.");
+  ComboAddress requestor1("192.0.2.1");
+  ComboAddress requestor2("192.0.2.2");
+  ComboAddress backend("192.0.2.42");
+  uint16_t qtype = QType::AAAA;
+  uint16_t size = 42;
+  unsigned int responseTime = 100 * 1000; /* 100ms */
+  struct timespec now;
+  gettime(&now);
+  NetmaskTree<DynBlock> emptyNMG;
+
+  size_t numberOfSeconds = 10;
+  size_t blockDuration = 60;
+  const auto action = DNSAction::Action::Drop;
+  const std::string reason = "Exceeded query ratio";
+  const uint16_t rcode = RCode::ServFail;
+
+  DynBlockRulesGroup dbrg;
+  dbrg.setQuiet(true);
+
+  /* block above 0.2 ServFail/Total ratio over numberOfSeconds seconds, no warning, minimum number of queries should be at least 51 */
+  dbrg.setRCodeRatio(rcode, 0.2, 0, numberOfSeconds, reason, blockDuration, action, 51);
+
+  {
+    /* insert 20 ServFail and 80 NoErrors from a given client in the last 10s
+       this should not trigger the rule */
+    g_rings.clear();
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+    g_dynblockNMG.setState(emptyNMG);
+
+    dh.rcode = rcode;
+    for (size_t idx = 0; idx < 20; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+    }
+    dh.rcode = RCode::NoError;
+    for (size_t idx = 0; idx < 80; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+    }
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100);
+
+    dbrg.apply(now);
+    BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+    BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+  }
+
+  {
+    /* insert just 50 FormErrs and nothing else, from a given client in the last 10s */
+    g_rings.clear();
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+    g_dynblockNMG.setState(emptyNMG);
+
+    dh.rcode = RCode::FormErr;
+    for (size_t idx = 0; idx < 50; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+    }
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50);
+
+    dbrg.apply(now);
+    BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+    BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+  }
+
+  {
+    /* insert 21 ServFails and 79 NoErrors from a given client in the last 10s
+       this should trigger the rule this time */
+    g_rings.clear();
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+    g_dynblockNMG.setState(emptyNMG);
+
+    dh.rcode = rcode;
+    for (size_t idx = 0; idx < 21; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+    }
+    dh.rcode = RCode::NoError;
+    for (size_t idx = 0; idx < 79; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+    }
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 100);
+
+    dbrg.apply(now);
+    BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 1);
+    BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) != nullptr);
+    BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor2) == nullptr);
+    const auto& block = g_dynblockNMG.getLocal()->lookup(requestor1)->second;
+    BOOST_CHECK_EQUAL(block.reason, reason);
+    BOOST_CHECK_EQUAL(block.until.tv_sec, now.tv_sec + blockDuration);
+    BOOST_CHECK(block.domain.empty());
+    BOOST_CHECK(block.action == action);
+    BOOST_CHECK_EQUAL(block.blocks, 0);
+    BOOST_CHECK_EQUAL(block.warning, false);
+  }
+
+  {
+    /* insert 11 ServFails and 39 NoErrors from a given client in the last 10s
+       this should NOT trigger the rule since we don't have more than 50 queries */
+    g_rings.clear();
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 0);
+    g_dynblockNMG.setState(emptyNMG);
+
+    dh.rcode = rcode;
+    for (size_t idx = 0; idx < 11; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+    }
+    dh.rcode = RCode::NoError;
+    for (size_t idx = 0; idx < 39; idx++) {
+      g_rings.insertResponse(now, requestor1, qname, qtype, responseTime, size, dh, backend);
+    }
+    BOOST_CHECK_EQUAL(g_rings.getNumberOfResponseEntries(), 50);
+
+    dbrg.apply(now);
+    BOOST_CHECK_EQUAL(g_dynblockNMG.getLocal()->size(), 0);
+    BOOST_CHECK(g_dynblockNMG.getLocal()->lookup(requestor1) == nullptr);
+  }
+}
+
 BOOST_AUTO_TEST_CASE(test_DynBlockRulesGroup_ResponseByteRate) {
   dnsheader dh;
   DNSName qname("rings.powerdns.com.");
index c9db4778f4094c7fd52531cb1991b9a0e51eda14..6b33ab0930eb0e80eb17e05fc125a34306dbad62 100644 (file)
@@ -88,4 +88,6 @@ message PBDNSMessage {
   optional bytes deviceId = 17;                // Device ID of the requestor (could be mac address IP address or e.g. IMEI)
   optional bool  newlyObservedDomain = 18;      // True if the domain has not been seen before
   optional string deviceName = 19;              // Device name of the requestor
+  optional uint32 fromPort = 20;                // Source port of the DNS query (client)
+  optional uint32 toPort = 21;                  // Destination port of the DNS query (server)
 }
index f280f4de985e98bb99fc7fcc4c6953a8acb1181c..c23db851de87b7b9c6a7b2213a8f8c79bb4c0b3d 100644 (file)
@@ -181,9 +181,13 @@ public:
     if(d_ourDB)
       delete d_keymetadb;
   }
+
+  static uint64_t dbdnssecCacheSizes(const std::string& str);
+  static void clearAllCaches();
+  static void clearCaches(const DNSName& name);
+
   bool doesDNSSEC();
   bool isSecuredZone(const DNSName& zone);
-  static uint64_t dbdnssecCacheSizes(const std::string& str);
   keyset_t getEntryPoints(const DNSName& zname);
   keyset_t getKeys(const DNSName& zone, bool useCache = true);
   DNSSECPrivateKey getKeyById(const DNSName& zone, unsigned int id);
@@ -198,8 +202,6 @@ public:
   bool checkNSEC3PARAM(const NSEC3PARAMRecordContent& ns3p, string& msg);
   bool setNSEC3PARAM(const DNSName& zname, const NSEC3PARAMRecordContent& n3p, const bool& narrow=false);
   bool unsetNSEC3PARAM(const DNSName& zname);
-  void clearAllCaches();
-  void clearCaches(const DNSName& name);
   bool getPreRRSIGs(UeberBackend& db, const DNSName& signer, const DNSName& qname, const DNSName& wildcardname, const QType& qtype, DNSResourceRecord::Place, vector<DNSZoneRecord>& rrsigs, uint32_t signTTL);
   bool isPresigned(const DNSName& zname);
   bool setPresigned(const DNSName& zname);
index 7541fdb656a53089a9b2281f8576e3eb6e625657..9e8f36c24017c350a0e575b6a6510f8f359db9bb 100644 (file)
@@ -122,7 +122,6 @@ string DLUptimeHandler(const vector<string>&parts, Utility::pid_t ppid)
 
 string DLPurgeHandler(const vector<string>&parts, Utility::pid_t ppid)
 {
-  DNSSECKeeper dk;
   ostringstream os;
   int ret=0;
 
@@ -130,14 +129,14 @@ string DLPurgeHandler(const vector<string>&parts, Utility::pid_t ppid)
     for (vector<string>::const_iterator i=++parts.begin();i<parts.end();++i) {
       ret+=purgeAuthCaches(*i);
       if(!boost::ends_with(*i, "$"))
-       dk.clearCaches(DNSName(*i));
+        DNSSECKeeper::clearCaches(DNSName(*i));
       else
-       dk.clearAllCaches(); // at least we do what we promise.. and a bit more!
+        DNSSECKeeper::clearAllCaches(); // at least we do what we promise.. and a bit more!
     }
   }
   else {
     ret = purgeAuthCaches();
-    dk.clearAllCaches();
+    DNSSECKeeper::clearAllCaches();
   }
 
   os<<ret;
index 3cecbfff79b042adaf768e603fceba32991b2861..ef37ac3ac88b75ba07713e12dfd2d1a4bf79aae9 100644 (file)
@@ -141,27 +141,37 @@ DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const DNSName& qnam
   }
 
   Policy pol;
-  if (!allEmpty) {
-    count = 0;
-    for(const auto& z : d_zones) {
-      if (zoneEnabled[count] && z->findExactNSPolicy(qname, pol)) {
-        // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
-        return pol;
-      }
+  if (allEmpty) {
+    return pol;
+  }
+
+  /* prepare the wildcard-based names */
+  std::vector<DNSName> wcNames;
+  wcNames.reserve(qname.countLabels());
+  DNSName s(qname);
+  while (s.chopOff()){
+    wcNames.emplace_back(g_wildcarddnsname+s);
+  }
+
+  count = 0;
+  for(const auto& z : d_zones) {
+    if (!zoneEnabled[count]) {
       ++count;
+      continue;
     }
 
-    DNSName s(qname);
-    while(s.chopOff()){
-      count = 0;
-      for(const auto& z : d_zones) {
-        if (zoneEnabled[count] && z->findExactNSPolicy(g_wildcarddnsname+s, pol)) {
-          // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
-          return pol;
-        }
-        ++count;
+    if (z->findExactNSPolicy(qname, pol)) {
+      // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
+      return pol;
+    }
+
+    for (const auto& wc : wcNames) {
+      if (z->findExactNSPolicy(wc, pol)) {
+        // cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
+        return pol;
       }
     }
+    ++count;
   }
 
   return pol;
@@ -187,7 +197,7 @@ DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const ComboAddress&
 
 DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies) const
 {
-  //  cout<<"Got question for "<<qname<<" from "<<ca.toString()<<endl;
+  // cout<<"Got question for "<<qname<<" from "<<ca.toString()<<endl;
   std::vector<bool> zoneEnabled(d_zones.size());
   size_t count = 0;
   bool allEmpty = true;
@@ -198,7 +208,7 @@ DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, co
       enabled = false;
     }
     else {
-      if (z->hasQNamePolicies()) {
+      if (z->hasQNamePolicies() || z->hasClientPolicies()) {
         allEmpty = false;
       }
       else {
@@ -211,35 +221,42 @@ DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, co
   }
 
   Policy pol;
-  if (!allEmpty) {
-    count = 0;
-    for(const auto& z : d_zones) {
-      if (zoneEnabled[count] && z->findExactQNamePolicy(qname, pol)) {
-        //      cerr<<"Had a hit on the name of the query"<<endl;
-        return pol;
-      }
+  if (allEmpty) {
+    return pol;
+  }
+
+  /* prepare the wildcard-based names */
+  std::vector<DNSName> wcNames;
+  wcNames.reserve(qname.countLabels());
+  DNSName s(qname);
+  while (s.chopOff()){
+    wcNames.emplace_back(g_wildcarddnsname+s);
+  }
+
+  count = 0;
+  for (const auto& z : d_zones) {
+    if (!zoneEnabled[count]) {
       ++count;
+      continue;
     }
 
-    DNSName s(qname);
-    while(s.chopOff()){
-      count = 0;
-      for(const auto& z : d_zones) {
-        if (zoneEnabled[count] && z->findExactQNamePolicy(g_wildcarddnsname+s, pol)) {
-          //      cerr<<"Had a hit on the name of the query"<<endl;
-          return pol;
-        }
-        ++count;
+    if (z->findExactQNamePolicy(qname, pol)) {
+      // cerr<<"Had a hit on the name of the query"<<endl;
+      return pol;
+    }
+
+    for (const auto& wc : wcNames) {
+      if (z->findExactQNamePolicy(wc, pol)) {
+        // cerr<<"Had a hit on the name of the query"<<endl;
+        return pol;
       }
     }
-  }
 
-  count = 0;
-  for(const auto& z : d_zones) {
-    if (zoneEnabled[count] && z->findClientPolicy(ca, pol)) {
-      //       cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
+    if (z->findClientPolicy(ca, pol)) {
+      // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
       return pol;
     }
+
     ++count;
   }
 
@@ -250,10 +267,10 @@ DNSFilterEngine::Policy DNSFilterEngine::getPostPolicy(const vector<DNSRecord>&
 {
   Policy pol;
   ComboAddress ca;
-  for(const auto& r : records) {
-    if(r.d_place != DNSResourceRecord::ANSWER)
+  for (const auto& r : records) {
+    if (r.d_place != DNSResourceRecord::ANSWER)
       continue;
-    if(r.d_type == QType::A) {
+    if (r.d_type == QType::A) {
       if (auto rec = getRR<ARecordContent>(r)) {
         ca = rec->getCA();
       }
@@ -266,13 +283,13 @@ DNSFilterEngine::Policy DNSFilterEngine::getPostPolicy(const vector<DNSRecord>&
     else
       continue;
 
-    for(const auto& z : d_zones) {
+    for (const auto& z : d_zones) {
       const auto zoneName = z->getName();
-      if(zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
+      if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
         continue;
       }
 
-      if(z->findResponsePolicy(ca, pol)) {
+      if (z->findResponsePolicy(ca, pol)) {
        return pol;
       }
     }
index 7b171efec995eccd1f7c5360aaa149c91e46105a..77b5667ee2b487f410712a3f45f0a3455815b76c 100644 (file)
@@ -306,11 +306,9 @@ union ComboAddress {
     return ntohs(sin4.sin_port);
   }
 
-  ComboAddress setPort(uint16_t port) const
+  void setPort(uint16_t port)
   {
-    ComboAddress ret(*this);
-    ret.sin4.sin_port=htons(port);
-    return ret;
+    sin4.sin_port = htons(port);
   }
 
   void reset()
index 74c3b0d2871103da043e9898869074974c7aa8dc..36239b0e14a779b788e7bbd0b6c19cab3fcef7be 100644 (file)
@@ -263,6 +263,7 @@ void RecursorLua4::postPrepareContext()
       pol.d_name = std::make_shared<std::string>(name);
     });
   d_lw->registerMember("policyKind", &DNSFilterEngine::Policy::d_kind);
+  d_lw->registerMember("policyType", &DNSFilterEngine::Policy::d_type);
   d_lw->registerMember("policyTTL", &DNSFilterEngine::Policy::d_ttl);
   d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyCustom",
     [](const DNSFilterEngine::Policy& pol) -> std::string {
index 927651c332c5324e43b470715e29438d63922052..b14a39dc931f6bb4f68a636032d3ba25f28c85ec 100644 (file)
@@ -78,9 +78,11 @@ public:
   
   /* tv will be updated to 'now' before run returns */
   /* timeout is in ms */
+  /* returns 0 on timeout, -1 in case of error (but all implementations
+     actually throw in that case) and the number of ready events otherwise */
   virtual int run(struct timeval* tv, int timeout=500) = 0;
 
-  /* timeout is in ms, 0 will return immediatly, -1 will block until at least one FD is ready */
+  /* timeout is in ms, 0 will return immediately, -1 will block until at least one FD is ready */
   virtual void getAvailableFDs(std::vector<int>& fds, int timeout) = 0;
 
   //! Add an fd to the read watch list - currently an fd can only be on one list at a time!
index e0e78992fbd1ec9ea85dcd7ef41e23cf6a55313f..6d59cff076d52cd93aa6bc925574974047b557a7 100644 (file)
@@ -784,10 +784,11 @@ int PacketHandler::trySuperMaster(const DNSPacket& p, const DNSName& tsigkeyname
 
 int PacketHandler::trySuperMasterSynchronous(const DNSPacket& p, const DNSName& tsigkeyname)
 {
-  ComboAddress remote = p.getRemote().setPort(53);
+  ComboAddress remote = p.getRemote();
   if(p.hasEDNSSubnet() && ::arg().contains("trusted-notification-proxy", remote.toString())) {
-    remote = p.getRealRemote().getNetwork().setPort(53);
+    remote = p.getRealRemote().getNetwork();
   }
+  remote.setPort(53);
 
   Resolver::res_t nsset;
   try {
index 987682b2bdb2121247e4b6003152de19c66538d1..ebfb3440a55872e28618b31e40ea3ab430e05ab9 100644 (file)
@@ -814,7 +814,8 @@ static void protobufLogQuery(uint8_t maskV4, uint8_t maskV6, const boost::uuids:
   }
 
   Netmask requestorNM(remote, remote.sin4.sin_family == AF_INET ? maskV4 : maskV6);
-  const ComboAddress& requestor = requestorNM.getMaskedNetwork();
+  ComboAddress requestor = requestorNM.getMaskedNetwork();
+  requestor.setPort(remote.getPort());
   RecProtoBufMessage message(DNSProtoBufMessage::Query, uniqueId, &requestor, &local, qname, qtype, qclass, id, tcp, len);
   message.setServerIdentity(SyncRes::s_serverID);
   message.setEDNSSubnet(ednssubnet, ednssubnet.isIPv4() ? maskV4 : maskV6);
@@ -1184,7 +1185,8 @@ static void startDoResolve(void *p)
 #ifdef HAVE_PROTOBUF
     if (checkProtobufExport(luaconfsLocal)) {
       Netmask requestorNM(dc->d_source, dc->d_source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-      const ComboAddress& requestor = requestorNM.getMaskedNetwork();
+      ComboAddress requestor = requestorNM.getMaskedNetwork();
+      requestor.setPort(dc->d_source.getPort());
       pbMessage = RecProtoBufMessage(RecProtoBufMessage::Response, dc->d_uuid, &requestor, &dc->d_destination, dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass, dc->d_mdp.d_header.id, dc->d_tcp, 0);
       pbMessage->setServerIdentity(SyncRes::s_serverID);
       pbMessage->setEDNSSubnet(dc->d_ednssubnet.source, dc->d_ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
@@ -1324,7 +1326,7 @@ static void startDoResolve(void *p)
     }
 
     // Check if the query has a policy attached to it
-    if (wantsRPZ) {
+    if (wantsRPZ && appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
       appliedPolicy = luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, dc->d_source, sr.d_discardedPolicies);
     }
 
@@ -1370,15 +1372,19 @@ static void startDoResolve(void *p)
 
       // Query got not handled for QNAME Policy reasons, now actually go out to find an answer
       try {
+        sr.d_appliedPolicy = appliedPolicy;
         res = sr.beginResolve(dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_mdp.d_qclass, ret);
         shouldNotValidate = sr.wasOutOfBand();
       }
-      catch(ImmediateServFailException &e) {
-        if(g_logCommonErrors)
+      catch(const ImmediateServFailException &e) {
+        if(g_logCommonErrors) {
           g_log<<Logger::Notice<<"Sending SERVFAIL to "<<dc->getRemote()<<" during resolve of '"<<dc->d_mdp.d_qname<<"' because: "<<e.reason<<endl;
+        }
         res = RCode::ServFail;
       }
-
+      catch(const PolicyHitException& e) {
+        res = -2;
+      }
       dq.validationState = sr.getValidationState();
 
       // During lookup, an NSDNAME or NSIP trigger was hit in RPZ
@@ -1422,7 +1428,7 @@ static void startDoResolve(void *p)
         }
       }
 
-      if (wantsRPZ) {
+      if (wantsRPZ && appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
         appliedPolicy = luaconfsLocal->dfe.getPostPolicy(ret, sr.d_discardedPolicies);
       }
 
@@ -1557,7 +1563,7 @@ static void startDoResolve(void *p)
             }
           }
         }
-        catch(ImmediateServFailException &e) {
+        catch(const ImmediateServFailException &e) {
           if(g_logCommonErrors)
             g_log<<Logger::Notice<<"Sending SERVFAIL to "<<dc->getRemote()<<" during validation of '"<<dc->d_mdp.d_qname<<"|"<<QType(dc->d_mdp.d_qtype).getName()<<"' because: "<<e.reason<<endl;
           pw.getHeader()->rcode=RCode::ServFail;
@@ -2381,7 +2387,8 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
 #ifdef HAVE_PROTOBUF
       if(t_protobufServers && logResponse && !(luaconfsLocal->protobufExportConfig.taggedOnly && pbMessage->getAppliedPolicy().empty() && pbMessage->getPolicyTags().empty())) {
         Netmask requestorNM(source, source.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
-        const ComboAddress& requestor = requestorNM.getMaskedNetwork();
+        ComboAddress requestor = requestorNM.getMaskedNetwork();
+        requestor.setPort(source.getPort());
         pbMessage->update(uniqueId, &requestor, &destination, false, dh->id);
         pbMessage->setEDNSSubnet(ednssubnet.source, ednssubnet.source.isIPv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
         if (g_useKernelTimestamp && tv.tv_sec) {
@@ -2936,18 +2943,21 @@ static void houseKeeping(void *)
        try {
          doSecPoll(&last_secpoll);
        }
-       catch(std::exception& e)
+       catch(const std::exception& e)
         {
           g_log<<Logger::Error<<"Exception while performing security poll: "<<e.what()<<endl;
         }
-        catch(PDNSException& e)
+        catch(const PDNSException& e)
         {
           g_log<<Logger::Error<<"Exception while performing security poll: "<<e.reason<<endl;
         }
-        catch(ImmediateServFailException &e)
+        catch(const ImmediateServFailException &e)
         {
           g_log<<Logger::Error<<"Exception while performing security poll: "<<e.reason<<endl;
         }
+        catch(const PolicyHitException& e) {
+          g_log<<Logger::Error<<"Policy hit while performing security poll"<<endl;
+        }
         catch(...)
         {
           g_log<<Logger::Error<<"Exception while performing security poll"<<endl;
@@ -3809,6 +3819,27 @@ static void setupNODGlobal()
 }
 #endif /* NOD_ENABLED */
 
+static void checkSocketDir(void)
+{
+  struct stat st;
+  string dir(::arg()["socket-dir"]);
+  string msg;
+
+  if (stat(dir.c_str(), &st) == -1) {
+    msg = "it does not exist or cannot access";
+  }
+  else if (!S_ISDIR(st.st_mode)) {
+    msg = "it is not a directory";
+  }
+  else if (access(dir.c_str(),  R_OK | W_OK | X_OK) != 0) {
+    msg = "cannot read, write or search";
+  } else {
+    return;
+  }
+  g_log << Logger::Error << "Problem with socket directory " << dir << ": " << msg << "; see https://docs.powerdns.com/recursor/upgrade.html#x-to-4-3-0-or-master" << endl;
+  _exit(1);
+}
+
 static int serviceMain(int argc, char*argv[])
 {
   g_log.setName(s_programname);
@@ -3967,6 +3998,12 @@ static int serviceMain(int argc, char*argv[])
 
   SyncRes::s_qnameminimization = ::arg().mustDo("qname-minimization");
 
+  if (SyncRes::s_qnameminimization) {
+    // With an empty cache, a rev ipv6 query with dnssec enabled takes
+    // almost 100 queries. Default maxqperq is 60.
+    SyncRes::s_maxqperq = std::max(SyncRes::s_maxqperq, static_cast<unsigned int>(100));
+  }
+
   SyncRes::s_hardenNXD = SyncRes::HardenNXD::DNSSEC;
   string value = ::arg()["nothing-below-nxdomain"];
   if (value == "yes") {
@@ -4053,14 +4090,14 @@ static int serviceMain(int argc, char*argv[])
   {
     SuffixMatchNode dontThrottleNames;
     vector<string> parts;
-    stringtok(parts, ::arg()["dont-throttle-names"]);
+    stringtok(parts, ::arg()["dont-throttle-names"], " ,");
     for (const auto &p : parts) {
       dontThrottleNames.add(DNSName(p));
     }
     g_dontThrottleNames.setState(std::move(dontThrottleNames));
 
     NetmaskGroup dontThrottleNetmasks;
-    stringtok(parts, ::arg()["dont-throttle-netmasks"]);
+    stringtok(parts, ::arg()["dont-throttle-netmasks"], " ,");
     for (const auto &p : parts) {
       dontThrottleNetmasks.addMask(Netmask(p));
     }
@@ -4205,6 +4242,8 @@ static int serviceMain(int argc, char*argv[])
       g_log<<Logger::Info<<"Chrooted to '"<<::arg()["chroot"]<<"'"<<endl;
   }
 
+  checkSocketDir();
+
   s_pidfname=::arg()["socket-dir"]+"/"+s_programname+".pid";
   if(!s_pidfname.empty())
     unlink(s_pidfname.c_str()); // remove possible old pid file
@@ -4695,7 +4734,7 @@ int main(int argc, char **argv)
     ::arg().set("udp-truncation-threshold", "Maximum UDP response size before we truncate")="1232";
     ::arg().set("edns-outgoing-bufsize", "Outgoing EDNS buffer size")="1232";
     ::arg().set("minimum-ttl-override", "Set under adverse conditions, a minimum TTL")="0";
-    ::arg().set("max-qperq", "Maximum outgoing queries per query")="50";
+    ::arg().set("max-qperq", "Maximum outgoing queries per query")="60";
     ::arg().set("max-total-msec", "Maximum total wall-clock time per query in milliseconds, 0 for unlimited")="7000";
     ::arg().set("max-recursion-depth", "Maximum number of internal recursion calls per query, 0 for unlimited")="40";
     ::arg().set("max-udp-queries-per-round", "Maximum number of UDP queries processed per recvmsg() round, before returning back to normal processing")="10000";
index cd0877d2eaf76a8ae438cd90c2ec883639a1e202..31fe67edf87c22cab3c81ca4eecb11648bf53d3e 100644 (file)
@@ -239,6 +239,13 @@ void DNSProtoBufMessage::setRequestor(const ComboAddress& requestor)
 #endif /* HAVE_PROTOBUF */
 }
 
+void DNSProtoBufMessage::setRequestorPort(uint16_t port)
+{
+#ifdef HAVE_PROTOBUF
+  d_message.set_fromport(port);
+#endif /* HAVE_PROTOBUF */
+}
+
 void DNSProtoBufMessage::setRequestorId(const std::string& requestorId)
 {
 #ifdef HAVE_PROTOBUF
@@ -286,6 +293,13 @@ void DNSProtoBufMessage::setResponder(const ComboAddress& responder)
 #endif /* HAVE_PROTOBUF */
 }
 
+void DNSProtoBufMessage::setResponderPort(uint16_t port)
+{
+#ifdef HAVE_PROTOBUF
+  d_message.set_toport(port);
+#endif /* HAVE_PROTOBUF */
+}
+
 void DNSProtoBufMessage::serialize(std::string& data) const
 {
 #ifdef HAVE_PROTOBUF
@@ -342,9 +356,11 @@ void DNSProtoBufMessage::update(const boost::uuids::uuid& uuid, const ComboAddre
 
   if (responder) {
     setResponder(*responder);
+    setResponderPort(responder->getPort());
   }
   if (requestor) {
     setRequestor(*requestor);
+    setRequestorPort(requestor->getPort());
   }
 }
 
index 86b47c11f075b8ea28b3baaaf3a1eff1749d2df2..0df3ee62bebd733ef413874c6fe767303084a287 100644 (file)
@@ -67,8 +67,10 @@ public:
   void serialize(std::string& data) const;
   void setRequestor(const std::string& requestor);
   void setRequestor(const ComboAddress& requestor);
+  void setRequestorPort(uint16_t port);
   void setResponder(const std::string& responder);
   void setResponder(const ComboAddress& responder);
+  void setResponderPort(uint16_t port);
   void setRequestorId(const std::string& requestorId);
   void setDeviceId(const std::string& deviceId);
   void setDeviceName(const std::string& deviceName);
index 3b595f2ab606209a63fd9e8faa8bf24037a3110f..962d25032c73151669f6a77cf47dc3b7cbc40d43 100644 (file)
@@ -1,6 +1,50 @@
 Changelogs for 4.3.x
 ====================
 
+.. changelog::
+  :version: 4.3.0-beta2
+  :released: 16th of January 2020
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8704
+
+    Add the source and destination ports to the protobuf msg.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8673
+
+    Debian postinst / do not fail on user creation if it already exists.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8685
+    :tickets:  8676
+
+    Parsing `dont-throttle-names` and `dont-throttle-netmasks` as comma separated lists. (costypetrisor)
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8692
+    :tickets: 8664
+
+    An Opt-Out NSEC3 RR only proves that there is no secure delegation.
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8670
+    :tickets: 8642
+
+    Fix wrong zoneCuts caused by cache only lookup.
+
+  .. change::
+    :tags: Improvements
+    :pullreq: 8675
+    :tickets: 8646
+
+    Increase default max-qperq.
+
 .. changelog::
   :version: 4.3.0-beta1
   :released: 12th of December 2019
index 4283b95a09162a823cae1f99ff2162be83ae1754..cbebe9f2ef239d9580ae5c597d4d5f12b8eaf937 100644 (file)
@@ -914,10 +914,12 @@ Maximum number of Packet Cache entries.
 ``max-qperq``
 -------------
 -  Integer
--  Default: 50
+-  Default: 60
 
 The maximum number of outgoing queries that will be sent out during the resolution of a single client query.
 This is used to limit endlessly chasing CNAME redirections.
+If qname-minimization is enabled, the number will be forced to be 100
+at a minimum to allow for the extra queries qname-minimization generates when the cache is empty.
 
 .. _setting-max-negative-ttl:
 
index 875b8ec429f0efcf5bec15c3988502761d28f793..a583aa68e49a3d4befac4ab850277ddf9f348f60 100644 (file)
@@ -428,9 +428,13 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
   zone2->setName("Unit test policy 1");
 
   const DNSName bad("bad.example.com.");
+  const DNSName badWildcard("*.bad-wildcard.example.com.");
+  const DNSName badUnderWildcard("sub.bad-wildcard.example.com.");
 
-  zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1.example.net.")}));
-  zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2.example.net.")}));
+  zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1a.example.net.")}));
+  zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2a.example.net.")}));
+  zone1->addQNameTrigger(badWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1b.example.net.")}));
+  zone2->addQNameTrigger(badUnderWildcard, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2b.example.net.")}));
 
   dfe.addZone(zone1);
   dfe.addZone(zone2);
@@ -447,7 +451,22 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
     BOOST_CHECK(record.d_class == QClass::IN);
     auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
     BOOST_CHECK(content != nullptr);
-    BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1.example.net.");
+    BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1a.example.net.");
+  }
+
+  {
+    /* zone 2 has an exact match for badUnderWildcard, but the wildcard from the first zone should match first */
+    const auto matchingPolicy = dfe.getQueryPolicy(badUnderWildcard, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+    BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+    BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+    auto records = matchingPolicy.getCustomRecords(badUnderWildcard, QType::A);
+    BOOST_CHECK_EQUAL(records.size(), 1U);
+    const auto& record = records.at(0);
+    BOOST_CHECK(record.d_type == QType::CNAME);
+    BOOST_CHECK(record.d_class == QClass::IN);
+    auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+    BOOST_CHECK(content != nullptr);
+    BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1b.example.net.");
   }
 
   {
@@ -462,7 +481,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
     BOOST_CHECK(record.d_class == QClass::IN);
     auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
     BOOST_CHECK(content != nullptr);
-    BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1.example.net.");
+    BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1a.example.net.");
   }
 
   {
@@ -477,7 +496,7 @@ BOOST_AUTO_TEST_CASE(test_multiple_filter_policies)
     BOOST_CHECK(record.d_class == QClass::IN);
     auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
     BOOST_CHECK(content != nullptr);
-    BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden2.example.net.");
+    BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden2a.example.net.");
   }
 
   {
index 5c116467c9dc20c99664868949ebbad7336a34ce..b354a943f133e508565db75735ee9e22656e451e 100644 (file)
@@ -361,11 +361,11 @@ void addNSECRecordToLW(const DNSName& domain, const DNSName& next, const std::se
   records.push_back(rec);
 }
 
-void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& hashedNext, const std::string& salt, unsigned int iterations, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records)
+void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& hashedNext, const std::string& salt, unsigned int iterations, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, bool optOut)
 {
   NSEC3RecordContent nrc;
   nrc.d_algorithm = 1;
-  nrc.d_flags = 0;
+  nrc.d_flags = optOut ? 1 : 0;
   nrc.d_iterations = iterations;
   nrc.d_salt = salt;
   nrc.d_nexthash = hashedNext;
@@ -383,15 +383,15 @@ void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& hashedNext
   records.push_back(rec);
 }
 
-void addNSEC3UnhashedRecordToLW(const DNSName& domain, const DNSName& zone, const std::string& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations)
+void addNSEC3UnhashedRecordToLW(const DNSName& domain, const DNSName& zone, const std::string& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations, bool optOut)
 {
   static const std::string salt = "deadbeef";
   std::string hashed = hashQNameWithSalt(salt, iterations, domain);
 
-  addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, next, salt, iterations, types, ttl, records);
+  addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, next, salt, iterations, types, ttl, records, optOut);
 }
 
-void addNSEC3NarrowRecordToLW(const DNSName& domain, const DNSName& zone, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations)
+void addNSEC3NarrowRecordToLW(const DNSName& domain, const DNSName& zone, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations, bool optOut)
 {
   static const std::string salt = "deadbeef";
   std::string hashed = hashQNameWithSalt(salt, iterations, domain);
@@ -399,7 +399,7 @@ void addNSEC3NarrowRecordToLW(const DNSName& domain, const DNSName& zone, const
   incrementHash(hashedNext);
   decrementHash(hashed);
 
-  addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, hashedNext, salt, iterations, types, ttl, records);
+  addNSEC3RecordToLW(DNSName(toBase32Hex(hashed)) + zone, hashedNext, salt, iterations, types, ttl, records, optOut);
 }
 
 void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest, testkeysset_t& keys)
@@ -419,7 +419,7 @@ void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest,
   dsAnchors[name].insert(keys[name].second);
 }
 
-int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth, int type, const testkeysset_t& keys, bool proveCut, boost::optional<time_t> now)
+int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth, int type, const testkeysset_t& keys, bool proveCut, boost::optional<time_t> now, bool nsec3, bool optOut)
 {
   if (type == QType::DS) {
     auth.chopOff();
@@ -438,12 +438,18 @@ int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth
         /* sign the SOA */
         addRRSIG(keys, res->d_records, auth, 300, false, boost::none, boost::none, now);
         /* add a NSEC denying the DS */
-        std::set<uint16_t> types = {QType::NSEC};
+        std::set<uint16_t> types = {nsec3 ? QType::NSEC : QType::NSEC3};
         if (proveCut) {
           types.insert(QType::NS);
         }
 
-        addNSECRecordToLW(domain, DNSName("z") + domain, types, 600, res->d_records);
+        if (!nsec3) {
+          addNSECRecordToLW(domain, DNSName("z") + domain, types, 600, res->d_records);
+        }
+        else {
+          addNSEC3UnhashedRecordToLW(domain, auth, (DNSName("z") + domain).toString(), types, 600, res->d_records, 10, optOut);
+        }
+
         addRRSIG(keys, res->d_records, auth, 300, false, boost::none, boost::none, now);
       }
     }
index 0976c1389de9a980d5b349fdafe18219d934ef57..2b909b242fdeea8662863ee5db727c275620eb73 100644 (file)
@@ -61,16 +61,16 @@ bool addDS(const DNSName& domain, uint32_t ttl, std::vector<DNSRecord>& records,
 
 void addNSECRecordToLW(const DNSName& domain, const DNSName& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records);
 
-void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& hashedNext, const std::string& salt, unsigned int iterations, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records);
+void addNSEC3RecordToLW(const DNSName& hashedName, const std::string& hashedNext, const std::string& salt, unsigned int iterations, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, bool optOut = false);
 
-void addNSEC3UnhashedRecordToLW(const DNSName& domain, const DNSName& zone, const std::string& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations = 10);
+void addNSEC3UnhashedRecordToLW(const DNSName& domain, const DNSName& zone, const std::string& next, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations = 10, bool optOut = false);
 
-void addNSEC3NarrowRecordToLW(const DNSName& domain, const DNSName& zone, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations = 10);
+void addNSEC3NarrowRecordToLW(const DNSName& domain, const DNSName& zone, const std::set<uint16_t>& types, uint32_t ttl, std::vector<DNSRecord>& records, unsigned int iterations = 10, bool OptOut = false);
 
 void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest, testkeysset_t& keys);
 
 void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest, testkeysset_t& keys, map<DNSName, dsmap_t>& dsAnchors);
 
-int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth, int type, const testkeysset_t& keys, bool proveCut = true, boost::optional<time_t> now = boost::none);
+int genericDSAndDNSKEYHandler(LWResult* res, const DNSName& domain, DNSName auth, int type, const testkeysset_t& keys, bool proveCut = true, boost::optional<time_t> now = boost::none, bool nsec3 = false, bool optOut = false);
 
 int basicRecordsForQnameMinimization(LWResult* res, const DNSName& domain, int type);
index 2a544ac886a7d4a45f291aae162b5a2cb826e2a7..bc6497c2d0114e3e79ca4e29cced6ba0eb27db8e 100644 (file)
@@ -521,9 +521,7 @@ BOOST_AUTO_TEST_CASE(test_nameserver_ipv4_rpz)
   g_luaconfs.setState(luaconfsCopy);
 
   vector<DNSRecord> ret;
-  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
-  BOOST_CHECK_EQUAL(res, -2);
-  BOOST_CHECK_EQUAL(ret.size(), 0U);
+  BOOST_CHECK_THROW(sr->beginResolve(target, QType(QType::A), QClass::IN, ret), PolicyHitException);
 }
 
 BOOST_AUTO_TEST_CASE(test_nameserver_ipv6_rpz)
@@ -564,9 +562,7 @@ BOOST_AUTO_TEST_CASE(test_nameserver_ipv6_rpz)
   g_luaconfs.setState(luaconfsCopy);
 
   vector<DNSRecord> ret;
-  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
-  BOOST_CHECK_EQUAL(res, -2);
-  BOOST_CHECK_EQUAL(ret.size(), 0U);
+  BOOST_CHECK_THROW(sr->beginResolve(target, QType(QType::A), QClass::IN, ret), PolicyHitException);
 }
 
 BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz)
@@ -608,9 +604,7 @@ BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz)
   g_luaconfs.setState(luaconfsCopy);
 
   vector<DNSRecord> ret;
-  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
-  BOOST_CHECK_EQUAL(res, -2);
-  BOOST_CHECK_EQUAL(ret.size(), 0U);
+  BOOST_CHECK_THROW(sr->beginResolve(target, QType(QType::A), QClass::IN, ret), PolicyHitException);
 }
 
 BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz_disabled)
index a056712935b2c9d60b122dcebae4842693f16534..e5d9efbf3a812cec90d307a8f69536583e9a8f09 100644 (file)
@@ -746,6 +746,229 @@ BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure)
   BOOST_CHECK_EQUAL(queriesCount, 7U);
 }
 
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_optout)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("powerdns.com.");
+  const ComboAddress targetAddr("192.0.2.42");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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++;
+
+    if (type == QType::DS) {
+      if (domain == target) {
+        setLWResult(res, 0, 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, DNSName("com."), 300);
+        /* closest encloser */
+        addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), DNSName("a.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        /* next closer */
+        addNSEC3UnhashedRecordToLW(DNSName("oowerdns.com."), DNSName("com."), DNSName("qowerdns.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        return 1;
+      }
+      else {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
+      }
+    }
+    else if (type == QType::DNSKEY) {
+      if (domain == g_rootdnsname || domain == DNSName("com.")) {
+        setLWResult(res, 0, true, false, true);
+        addDNSKEY(keys, domain, 300, res->d_records);
+        addRRSIG(keys, res->d_records, domain, 300);
+        return 1;
+      }
+      else {
+        setLWResult(res, 0, 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);
+        return 1;
+      }
+    }
+    else {
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+        addDS(DNSName("com."), 300, res->d_records, keys);
+        addRRSIG(keys, res->d_records, DNSName("."), 300);
+        addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      }
+      else if (ip == ComboAddress("192.0.2.1:53")) {
+        if (domain == DNSName("com.")) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        }
+        else {
+          setLWResult(res, 0, false, false, true);
+          addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600);
+          /* no DS */
+          /* closest encloser */
+          addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), DNSName("a.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          /* next closer */
+          addNSEC3UnhashedRecordToLW(DNSName("oowerdns.com."), DNSName("com."), DNSName("qowerdns.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600);
+        }
+        return 1;
+      }
+      else if (ip == ComboAddress("192.0.2.2:53")) {
+        setLWResult(res, 0, true, false, true);
+        if (type == QType::NS) {
+          addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.");
+        }
+        else {
+          addRecordToLW(res, domain, QType::A, targetAddr.toString());
+        }
+        return 1;
+      }
+    }
+
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 6);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::DS), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NoError);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 6);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(queriesCount, 4);
+}
+
+BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nxd_optout)
+{
+  std::unique_ptr<SyncRes> sr;
+  initSR(sr, true);
+
+  setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+  primeHints();
+  const DNSName target("powerdns.com.");
+  const ComboAddress targetAddr("192.0.2.42");
+  testkeysset_t keys;
+
+  auto luaconfsCopy = g_luaconfs.getCopy();
+  luaconfsCopy.dsAnchors.clear();
+  generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+  generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+  g_luaconfs.setState(luaconfsCopy);
+
+  size_t queriesCount = 0;
+
+  sr->setAsyncCallback([target, targetAddr, &queriesCount, keys](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++;
+
+    if (type == QType::DS) {
+      if (domain == target) {
+        setLWResult(res, RCode::NXDomain, true, false, true);
+        addRecordToLW(res, DNSName("com."), QType::SOA, "a.gtld-servers.com. hostmastercom. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        /* closest encloser */
+        addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), DNSName("a.com.").toStringNoDot(), {QType::SOA, QType::NS, QType::DS, QType::DNSKEY, QType::RRSIG, QType::NSEC3PARAM}, 600, res->d_records, 10, true);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        /* next closer */
+        addNSEC3UnhashedRecordToLW(DNSName("oowerdns.com."), DNSName("com."), DNSName("qowerdns.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+        addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        return 1;
+      }
+      else {
+        return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, true, boost::none, true, true);
+      }
+    }
+    else if (type == QType::DNSKEY) {
+      if (domain == g_rootdnsname || domain == DNSName("com.")) {
+        setLWResult(res, 0, true, false, true);
+        addDNSKEY(keys, domain, 300, res->d_records);
+        addRRSIG(keys, res->d_records, domain, 300);
+        return 1;
+      }
+      else {
+        setLWResult(res, 0, true, false, true);
+        addRecordToLW(res, DNSName("com."), QType::SOA, "a.gtld-servers.com. hostmastercom. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+        return 1;
+      }
+    }
+    else {
+      if (isRootServer(ip)) {
+        setLWResult(res, 0, false, false, true);
+        addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+        addDS(DNSName("com."), 300, res->d_records, keys);
+        addRRSIG(keys, res->d_records, DNSName("."), 300);
+        addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        return 1;
+      }
+      else if (ip == ComboAddress("192.0.2.1:53")) {
+        if (domain == DNSName("com.")) {
+          setLWResult(res, 0, true, false, true);
+          addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com.");
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+        }
+        else {
+          setLWResult(res, RCode::NXDomain, true, false, true);
+          addRecordToLW(res, DNSName("com."), QType::SOA, "a.gtld-servers.com. hostmastercom. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          /* closest encloser */
+          addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), DNSName("a.com.").toStringNoDot(), {QType::SOA, QType::NS, QType::DS, QType::DNSKEY, QType::RRSIG, QType::NSEC3PARAM}, 600, res->d_records, 10, true);
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+          /* next closer */
+          addNSEC3UnhashedRecordToLW(DNSName("oowerdns.com."), DNSName("com."), DNSName("qowerdns.com.").toStringNoDot(), {QType::NS}, 600, res->d_records, 10, true);
+          addRRSIG(keys, res->d_records, DNSName("com."), 300);
+        }
+        return 1;
+      }
+    }
+
+    return 0;
+  });
+
+  vector<DNSRecord> ret;
+  int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 6);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(queriesCount, 6);
+
+  /* again, to test the cache */
+  ret.clear();
+  res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+  BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+  BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure);
+  BOOST_REQUIRE_EQUAL(ret.size(), 6);
+  BOOST_CHECK(ret[0].d_type == QType::SOA);
+  BOOST_CHECK_EQUAL(queriesCount, 6);
+}
+
 BOOST_AUTO_TEST_CASE(test_dnssec_secure_direct_ds)
 {
   /*
index 5ebf9ef7d34738bb4c42b5b9d8873e06eaea572c..55d516253f8512e4f0ad626f445f632259c1c65a 100644 (file)
@@ -190,7 +190,7 @@ try {
   bool showflags = false;
   bool hidesoadetails = false;
   bool doh = false;
-  bool stdin = false;
+  bool fromstdin = false;
   boost::optional<Netmask> ednsnm;
   uint16_t xpfcode = 0, xpfversion = 0, xpfproto = 0;
   char *xpfsrc = NULL, *xpfdst = NULL;
@@ -262,7 +262,7 @@ try {
   if (*argv[1] == 'h') {
     doh = true;
   } else if(strcmp(argv[1], "stdin") == 0) {
-    stdin = true;
+    fromstdin = true;
   } else {
     dest = ComboAddress(argv[1] + (*argv[1] == '@'), atoi(argv[2]));
   }
@@ -300,7 +300,7 @@ try {
 #else
     throw PDNSException("please link sdig against libcurl for DoH support");
 #endif
-  } else if (stdin) {
+  } else if (fromstdin) {
     std::istreambuf_iterator<char> begin(std::cin), end;
     reply = string(begin, end);
     printReply(reply, showflags, hidesoadetails);
index 4e031a002a1813606f6e030b06bf180e7ed118cc..06a9b2f0287b52b94e85bf309807d78facab36bb 100644 (file)
@@ -733,6 +733,7 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
         QLOG("Delegation seen, continue at step 1");
         break;
       }
+
       if (res != RCode::NoError) {
         // Case 5: unexpected answer
         QLOG("Step5: other rcode, last effort final resolve");
@@ -763,7 +764,7 @@ int SyncRes::doResolve(const DNSName &qname, const QType &qtype, vector<DNSRecor
  * \param beenthere
  * \param fromCache tells the caller the result came from the cache, may be nullptr
  * \param stopAtDelegation if non-nullptr and pointed-to value is Stop requests the callee to stop at a delegation, if so pointed-to value is set to Stopped
- * \return DNS RCODE or -1 (Error) or -2 (RPZ hit)
+ * \return DNS RCODE or -1 (Error)
  */
 int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, set<GetBestNSAnswer>& beenthere, vState& state, bool *fromCache, StopAtDelegation *stopAtDelegation)
 {
@@ -885,9 +886,6 @@ int SyncRes::doResolveNoQNameMinimization(const DNSName &qname, const QType &qty
 
   LOG(prefix<<qname<<": failed (res="<<res<<")"<<endl);
 
-  if (res == -2)
-    return res;
-
   return res<0 ? RCode::ServFail : res;
 }
 
@@ -923,46 +921,52 @@ vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth,
   d_requireAuthData = false;
   d_DNSSECValidationRequested = false;
 
-  vState newState = Indeterminate;
-  res_t resv4;
-  // If IPv4 ever becomes second class, we should revisit this
-  if (doResolve(qname, QType::A, resv4, depth+1, beenthere, newState) == 0) {  // this consults cache, OR goes out
-    for (auto const &i : resv4) {
-      if (i.d_type == QType::A) {
-        if (auto rec = getRR<ARecordContent>(i)) {
-          ret.push_back(rec->getCA(53));
+  try {
+    vState newState = Indeterminate;
+    res_t resv4;
+    // If IPv4 ever becomes second class, we should revisit this
+    if (doResolve(qname, QType::A, resv4, depth+1, beenthere, newState) == 0) {  // this consults cache, OR goes out
+      for (auto const &i : resv4) {
+        if (i.d_type == QType::A) {
+          if (auto rec = getRR<ARecordContent>(i)) {
+            ret.push_back(rec->getCA(53));
+          }
         }
       }
     }
-  }
-  if (s_doIPv6) {
-    if (ret.empty()) {
-      // We did not find IPv4 addresses, try to get IPv6 ones
-      newState = Indeterminate;
-      res_t resv6;
-      if (doResolve(qname, QType::AAAA, resv6, depth+1, beenthere, newState) == 0) {  // this consults cache, OR goes out
-        for (const auto &i : resv6) {
-          if (i.d_type == QType::AAAA) {
-            if (auto rec = getRR<AAAARecordContent>(i))
-              ret.push_back(rec->getCA(53));
+    if (s_doIPv6) {
+      if (ret.empty()) {
+        // We did not find IPv4 addresses, try to get IPv6 ones
+        newState = Indeterminate;
+        res_t resv6;
+        if (doResolve(qname, QType::AAAA, resv6, depth+1, beenthere, newState) == 0) {  // this consults cache, OR goes out
+          for (const auto &i : resv6) {
+            if (i.d_type == QType::AAAA) {
+              if (auto rec = getRR<AAAARecordContent>(i))
+                ret.push_back(rec->getCA(53));
+            }
           }
         }
-      }
-    } else {
-      // We have some IPv4 records, don't bother with going out to get IPv6, but do consult the cache
-      // Once IPv6 adoption matters, this needs to be revisited
-      res_t cset;
-      if (t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_cacheRemote) > 0) {
-        for (const auto &i : cset) {
-          if (i.d_ttl > (unsigned int)d_now.tv_sec ) {
-            if (auto rec = getRR<AAAARecordContent>(i)) {
-              ret.push_back(rec->getCA(53));
+      } else {
+        // We have some IPv4 records, don't bother with going out to get IPv6, but do consult the cache
+        // Once IPv6 adoption matters, this needs to be revisited
+        res_t cset;
+        if (t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_cacheRemote) > 0) {
+          for (const auto &i : cset) {
+            if (i.d_ttl > (unsigned int)d_now.tv_sec ) {
+              if (auto rec = getRR<AAAARecordContent>(i)) {
+                ret.push_back(rec->getCA(53));
+              }
             }
           }
         }
       }
     }
   }
+  catch (const PolicyHitException& e) {
+    /* we ignore a policy hit while trying to retrieve the addresses
+       of a NS and keep processing the current query */
+  }
 
   d_requireAuthData = oldRequireAuthData;
   d_DNSSECValidationRequested = oldValidationRequested;
@@ -1417,7 +1421,7 @@ void SyncRes::computeNegCacheValidationStatus(const NegCache::NegCacheEntry* ne,
     vState neValidationState = ne->d_validationState;
     dState expectedState = res == RCode::NXDomain ? NXDOMAIN : NXQTYPE;
     dState denialState = getDenialValidationState(*ne, state, expectedState, false);
-    updateDenialValidationState(neValidationState, ne->d_name, state, denialState, expectedState, qtype == QType::DS);
+    updateDenialValidationState(neValidationState, ne->d_name, state, denialState, expectedState, qtype == QType::DS || expectedState == NXDOMAIN);
   }
   if (state != Indeterminate) {
     /* validation succeeded, let's update the cache entry so we don't have to validate again */
@@ -1790,7 +1794,13 @@ static void addNXNSECS(vector<DNSRecord>&ret, const vector<DNSRecord>& records)
 
 bool SyncRes::nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& nameservers)
 {
-  if(d_wantsRPZ) {
+  /* we skip RPZ processing if:
+     - it was disabled (d_wantsRPZ is false) ;
+     - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
+     the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
+     process any further RPZ rules.
+  */
+  if (d_wantsRPZ && d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
     for (auto const &ns : nameservers) {
       d_appliedPolicy = dfe.getProcessingPolicy(ns.first, d_discardedPolicies);
       if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
@@ -1813,7 +1823,13 @@ bool SyncRes::nameserversBlockedByRPZ(const DNSFilterEngine& dfe, const NsSet& n
 
 bool SyncRes::nameserverIPBlockedByRPZ(const DNSFilterEngine& dfe, const ComboAddress& remoteIP)
 {
-  if (d_wantsRPZ) {
+  /* we skip RPZ processing if:
+     - it was disabled (d_wantsRPZ is false) ;
+     - we already got a RPZ hit (d_appliedPolicy.d_type != DNSFilterEngine::PolicyType::None) since
+     the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
+     process any further RPZ rules.
+  */
+  if (d_wantsRPZ && d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
     d_appliedPolicy = dfe.getProcessingPolicy(remoteIP, d_discardedPolicies);
     if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
       LOG(" (blocked by RPZ policy '"+(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")+"')");
@@ -2826,8 +2842,21 @@ void SyncRes::updateDenialValidationState(vState& neValidationState, const DNSNa
   else {
     if (denialState == OPTOUT && allowOptOut) {
       LOG(d_prefix<<"OPT-out denial found for "<<neName<<endl);
-      neValidationState = Secure;
-      return;
+      /* rfc5155 states:
+         "The AD bit, as defined by [RFC4035], MUST NOT be set when returning a
+         response containing a closest (provable) encloser proof in which the
+         NSEC3 RR that covers the "next closer" name has the Opt-Out bit set.
+
+         This rule is based on what this closest encloser proof actually
+         proves: names that would be covered by the Opt-Out NSEC3 RR may or
+         may not exist as insecure delegations.  As such, not all the data in
+         responses containing such closest encloser proofs will have been
+         cryptographically verified, so the AD bit cannot be set."
+
+         At best the Opt-Out NSEC3 RR proves that there is no signed DS (so no
+         secure delegation).
+      */
+      neValidationState = Insecure;
     }
     else if (denialState == INSECURE) {
       LOG(d_prefix<<"Insecure denial found for "<<neName<<", returning Insecure"<<endl);
@@ -2885,7 +2914,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
 
       if (state == Secure) {
         dState denialState = getDenialValidationState(ne, state, NXDOMAIN, false);
-        updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, NXDOMAIN, false);
+        updateDenialValidationState(ne.d_validationState, ne.d_name, state, denialState, NXDOMAIN, true);
       }
       else {
         ne.d_validationState = state;
@@ -3042,6 +3071,9 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co
         if (denialState == NXQTYPE || denialState == OPTOUT || denialState == INSECURE) {
           ne.d_ttd = lowestTTL + d_now.tv_sec;
           ne.d_validationState = Secure;
+          if (denialState == OPTOUT) {
+            ne.d_validationState = Insecure;
+          }
           LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
 
           if(!wasVariable()) {
@@ -3369,12 +3401,11 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
 
     nameservers.clear();
     for (auto const &nameserver : nsset) {
-      if (d_wantsRPZ) {
+      if (d_wantsRPZ && d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
         d_appliedPolicy = dfe.getProcessingPolicy(nameserver, d_discardedPolicies);
         if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
           LOG("however "<<nameserver<<" was blocked by RPZ policy '"<<(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")<<"'"<<endl);
-          *rcode = -2;
-          return true;
+          throw PolicyHitException();
         }
       }
       nameservers.insert({nameserver, {{}, false}});
@@ -3391,7 +3422,6 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
 
 /** returns:
  *  -1 in case of no results
- *  -2 when a FilterEngine Policy was hit
  *  rcode otherwise
  */
 int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, const DNSName &qname, const QType &qtype,
@@ -3408,7 +3438,8 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
   LOG(prefix<<qname<<": Cache consultations done, have "<<(unsigned int)nameservers.size()<<" NS to contact");
 
   if (nameserversBlockedByRPZ(luaconfsLocal->dfe, nameservers)) {
-    return -2;
+    /* RPZ hit */
+    throw PolicyHitException();
   }
 
   LOG(endl);
@@ -3492,8 +3523,10 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
             }
           }
           LOG(endl);
-          if (hitPolicy) //implies d_wantsRPZ
-            return -2;
+          if (hitPolicy) { //implies d_wantsRPZ
+            /* RPZ hit */
+            throw PolicyHitException();
+          }
         }
 
         for(remoteIP = remoteIPs.cbegin(); remoteIP != remoteIPs.cend(); ++remoteIP) {
@@ -3647,6 +3680,10 @@ int directResolve(const DNSName& qname, const QType& qtype, int qclass, vector<D
     g_log<<Logger::Error<<"Failed to resolve "<<qname.toLogString()<<", got ImmediateServFailException: "<<e.reason<<endl;
     ret.clear();
   }
+  catch(const PolicyHitException& e) {
+    g_log<<Logger::Error<<"Failed to resolve "<<qname.toLogString()<<", got a policy hit"<<endl;
+    ret.clear();
+  }
   catch(const std::exception& e) {
     g_log<<Logger::Error<<"Failed to resolve "<<qname.toLogString()<<", got STL error: "<<e.what()<<endl;
     ret.clear();
@@ -3684,6 +3721,10 @@ int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback) {
   catch(const ImmediateServFailException& e) {
     g_log<<Logger::Error<<"Failed to update . records, got an exception: "<<e.reason<<endl;
   }
+  catch(const PolicyHitException& e) {
+    g_log<<Logger::Error<<"Failed to update . records, got a policy hit"<<endl;
+    ret.clear();
+  }
   catch(const std::exception& e) {
     g_log<<Logger::Error<<"Failed to update . records, got an exception: "<<e.what()<<endl;
   }
index 2e0c399b494e8921faee52d464b392301ce3ef18..411ad3dd9e6af3e1a5b3bfac00d1f229805d19ab 100644 (file)
@@ -217,15 +217,16 @@ public:
     return i->value;
   }
 
-  counter_t incr(const ComboAddress& t, const struct timeval & now)
+  counter_t incr(const ComboAddress& address, const struct timeval& now)
   {
-    auto i = d_cont.insert(t).first;
+    auto i = d_cont.insert(address).first;
 
     if (i->value < std::numeric_limits<counter_t>::max()) {
       i->value++;
     }
     auto &ind = d_cont.get<ComboAddress>();
-    ind.modify(i, [t = now.tv_sec](value_t &val) { val.last = t;});
+    time_t tm = now.tv_sec;
+    ind.modify(i, [tm](value_t &val) { val.last = tm; });
     return i->value;
   }
 
@@ -1054,6 +1055,10 @@ public:
   string reason; //! Print this to tell the user what went wrong
 };
 
+class PolicyHitException
+{
+};
+
 typedef boost::circular_buffer<ComboAddress> addrringbuf_t;
 extern thread_local std::unique_ptr<addrringbuf_t> t_servfailremotes, t_largeanswerremotes, t_remotes, t_bogusremotes, t_timeouts;
 
index b49b362d3c89e1c5575a1315d3e974f4c098d56d..d82c2925aad44ad250ad8c680d7b6733b59c6ef1 100644 (file)
@@ -356,13 +356,13 @@ void *TCPNameserver::doConnection(void *data)
         else
           remote_text = packet->getRemote().toString();
         g_log << Logger::Notice<<"TCP Remote "<< remote_text <<" wants '" << packet->qdomain<<"|"<<packet->qtype.getName() <<
-        "', do = " <<packet->d_dnssecOk <<", bufsize = "<< packet->getMaxReplyLen()<<": ";
+        "', do = " <<packet->d_dnssecOk <<", bufsize = "<< packet->getMaxReplyLen();
       }
 
       if(PC.enabled()) {
         if(packet->couldBeCached() && PC.get(*packet, *cached)) { // short circuit - does the PacketCache recognize this question?
           if(logDNSQueries)
-            g_log<<"packetcache HIT"<<endl;
+            g_log<<"packetcache HIT"<<endl;
           cached->setRemote(&packet->d_remote);
           cached->d.id=packet->d.id;
           cached->d.rd=packet->d.rd; // copy in recursion desired bit
@@ -372,7 +372,11 @@ void *TCPNameserver::doConnection(void *data)
           continue;
         }
         if(logDNSQueries)
-            g_log<<"packetcache MISS"<<endl;
+            g_log<<": packetcache MISS"<<endl;
+      } else {
+        if (logDNSQueries) {
+          g_log<<endl;
+        }
       }
       {
         Lock l(&s_plock);
@@ -593,7 +597,7 @@ int TCPNameserver::doAXFR(const DNSName &target, std::unique_ptr<DNSPacket>& q,
   }
 
   DNSSECKeeper dk(&db);
-  dk.clearCaches(target);
+  DNSSECKeeper::clearCaches(target);
   bool securedZone = dk.isSecuredZone(target);
   bool presignedZone = dk.isPresigned(target);
 
@@ -1110,7 +1114,7 @@ int TCPNameserver::doIXFR(std::unique_ptr<DNSPacket>& q, int outsock)
   NSEC3PARAMRecordContent ns3pr;
   bool narrow;
 
-  dk.clearCaches(q->qdomain);
+  DNSSECKeeper::clearCaches(q->qdomain);
   bool securedZone = dk.isSecuredZone(q->qdomain);
   if(dk.getNSEC3PARAM(q->qdomain, &ns3pr, &narrow)) {
     if(narrow) {
index 1621eefa3e4b2dea5fdef59f92a33e104a1b5210..4e792aa4c6ee694bdc46233291a85e077c087a24 100644 (file)
@@ -633,6 +633,9 @@ bool UeberBackend::get(DNSZoneRecord &rr)
     d_answers.clear();
     return false;
   }
+
+  rr.dr.d_place=DNSResourceRecord::ANSWER;
+
   d_ancount++;
   d_answers.push_back(rr);
   return true;
index 8bbacc3a9f743f2c325b236bd130782e8a9e139e..b8ce53a8d277bb1ad4858939a56bec34ba8b627a 100644 (file)
@@ -535,7 +535,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
   if (needWildcardProof) {
     /* We now need to look for a NSEC3 covering the closest (provable) encloser
        RFC 5155 section-7.2.1
-       FRC 7129 section-5.5
+       RFC 7129 section-5.5
     */
     LOG("Now looking for the closest encloser for "<<qname<<endl);
 
@@ -599,7 +599,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
 
   if (found == true) {
     /* now that we have found the closest (provable) encloser,
-       we can construct the next closer (FRC7129 section-5.5) name
+       we can construct the next closer (RFC7129 section-5.5) name
        and look for a NSEC3 RR covering it */
     unsigned int labelIdx = qname.countLabels() - closestEncloser.countLabels();
     if (labelIdx >= 1) {
@@ -627,7 +627,7 @@ dState getDenial(const cspmap_t &validrrsets, const DNSName& qname, const uint16
               LOG("Denies existence of name "<<qname<<"/"<<QType(qtype).getName());
               nextCloserFound = true;
 
-              if (qtype == QType::DS && nsec3->d_flags & 1) {
+              if ((qtype == QType::DS || qtype == 0) && nsec3->d_flags & 1) {
                 LOG(" but is opt-out!");
                 isOptOut = true;
               }
index a7a2850b4b24969e8086831cb911d1b96d963617..e68a3b3a9e19cedb07d281cadcd9c5d5f842c7e2 100644 (file)
@@ -1747,8 +1747,7 @@ static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
       throw ApiException("Deleting domain '"+zonename.toString()+"' failed: backend delete failed/unsupported");
 
     // clear caches
-    DNSSECKeeper dk(&B);
-    dk.clearCaches(zonename);
+    DNSSECKeeper::clearCaches(zonename);
     purgeAuthCaches(zonename.toString() + "$");
 
     // empty body on success
index 32020b48affd22e40f8f5c39bcd5c8a1b903a275..01eb5332edab98a2dc12818aba7df39f7b7a6ce4 100644 (file)
@@ -57,6 +57,7 @@ class DNSDistTest(AssertEqualDNSMessageMixin, unittest.TestCase):
     _healthCheckName = 'a.root-servers.net.'
     _healthCheckCounter = 0
     _answerUnexpected = True
+    _checkConfigExpectedOutput = None
 
     @classmethod
     def startResponders(cls):
@@ -91,7 +92,10 @@ class DNSDistTest(AssertEqualDNSMessageMixin, 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))
-        expectedOutput = ('Configuration \'%s\' OK!\n' % (confFile)).encode()
+        if cls._checkConfigExpectedOutput is not None:
+          expectedOutput = cls._checkConfigExpectedOutput
+        else:
+          expectedOutput = ('Configuration \'%s\' OK!\n' % (confFile)).encode()
         if output != expectedOutput:
             raise AssertionError('dnsdist --check-config failed: %s' % output)
 
index bb309b76387b98ce4489863eb5ee6cd35f4ee685..6be7d4f3c0c55488df0740ab62c51ff94a5abc6e 100644 (file)
@@ -426,6 +426,8 @@ class TestAdvancedTruncateAnyAndTCP(DNSDistTest):
         """
         name = 'anytruncatetcp.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'ANY', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
 
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
@@ -494,6 +496,8 @@ class TestAdvancedAndNot(DNSDistTest):
         """
         name = 'andnot.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'TXT', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
 
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.NOTIMP)
@@ -533,6 +537,7 @@ class TestAdvancedOr(DNSDistTest):
         """
         name = 'aorudp.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'AAAA', 'IN')
+        query.flags &= ~dns.flags.RD
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
                                     3600,
@@ -565,6 +570,7 @@ class TestAdvancedOr(DNSDistTest):
         """
         name = 'aorudp.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
 
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.NOTIMP)
@@ -899,6 +905,7 @@ class TestAdvancedQPSNone(DNSDistTest):
         """
         name = 'qpsnone.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -925,6 +932,7 @@ class TestAdvancedNMGRule(DNSDistTest):
         """
         name = 'nmgrule.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -951,6 +959,7 @@ class TestDSTPortRule(DNSDistTest):
 
         name = 'dstportrule.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -993,6 +1002,7 @@ class TestAdvancedLabelsCountRule(DNSDistTest):
         # more than 6 labels, the query should be refused
         name = 'not.ok.labelscount.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -1004,6 +1014,7 @@ class TestAdvancedLabelsCountRule(DNSDistTest):
         # less than 5 labels, the query should be refused
         name = 'labelscountadvanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -1045,6 +1056,7 @@ class TestAdvancedWireLengthRule(DNSDistTest):
         # too short, the query should be refused
         name = 'short.qnamewirelength.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -1056,6 +1068,7 @@ class TestAdvancedWireLengthRule(DNSDistTest):
         # too long, the query should be refused
         name = 'toolongtobevalid.qnamewirelength.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -1098,6 +1111,7 @@ class TestAdvancedIncludeDir(DNSDistTest):
         # this one should be refused
         name = 'notincludedir.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -1239,6 +1253,8 @@ class TestAdvancedLuaTruncated(DNSDistTest):
         """
         name = 'tc.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
                                     3600,
@@ -1363,6 +1379,7 @@ class TestAdvancedRD(DNSDistTest):
         query = dns.message.make_query(name, 'A', 'IN')
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
+        expectedResponse.flags |= dns.flags.RA
 
         for method in ("sendUDPQuery", "sendTCPQuery"):
             sender = getattr(self, method)
@@ -1657,14 +1674,15 @@ class TestAdvancedEDNSVersionRule(DNSDistTest):
     addAction(EDNSVersionRule(0), ERCodeAction(DNSRCode.BADVERS))
     """
 
-    def testDropped(self):
+    def testBadVers(self):
         """
-        Advanced: A question with ECS version larger than 0 is dropped
+        Advanced: A question with ECS version larger than 0 yields BADVERS
         """
 
         name = 'ednsversionrule.advanced.tests.powerdns.com.'
 
         query = dns.message.make_query(name, 'A', 'IN', use_edns=1)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.BADVERS)
 
index 24cbc549f0b01fb77f9a7ccca0c86be82be83a91..c2dc85f426bb51063c4ab6715a25306b6842f1d9 100644 (file)
@@ -90,6 +90,8 @@ class TestBasics(DNSDistTest):
         """
         name = 'any.tests.powerdns.com.'
         query = dns.message.make_query(name, 'ANY', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.flags |= dns.flags.TC
 
@@ -201,6 +203,7 @@ class TestBasics(DNSDistTest):
         """
         name = 'evil4242.regex.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -243,6 +246,7 @@ class TestBasics(DNSDistTest):
         """
         name = 'nameAndQtype.tests.powerdns.com.'
         query = dns.message.make_query(name, 'TXT', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.NOTIMP)
 
@@ -396,6 +400,7 @@ class TestBasics(DNSDistTest):
         """
         name = 'dnsname.addaction.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -410,6 +415,7 @@ class TestBasics(DNSDistTest):
         """
         for name in ['dnsname-table{}.addaction.powerdns.com.'.format(i) for i in range(1,2)]:
             query = dns.message.make_query(name, 'A', 'IN')
+            query.flags &= ~dns.flags.RD
             expectedResponse = dns.message.make_response(query)
             expectedResponse.set_rcode(dns.rcode.REFUSED)
 
index 5f209f5d3d75e17ea5294ea9311b7ecb2d7169dc..bb27fde2861e192435a9b3eecaf2de0f24278c0b 100644 (file)
@@ -321,6 +321,7 @@ class TestDOH(DNSDistDOHTest):
         name = 'refused.doh.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
         query.id = 0
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -732,6 +733,9 @@ class TestDOHOverHTTP(DNSDistDOHTest):
     addDOHLocal("127.0.0.1:%s")
     """
     _config_params = ['_testServerPort', '_dohServerPort']
+    _checkConfigExpectedOutput = b"""No certificate provided for DoH endpoint 127.0.0.1:8480, running in DNS over HTTP mode instead of DNS over HTTPS
+Configuration 'configs/dnsdist_TestDOHOverHTTP.conf' OK!
+"""
 
     def testDOHSimple(self):
         """
index 06260357451cb295abb7aa0b3843486b656b184d..fad4a22426e7e233f8248d67410c13cc23c6e656 100644 (file)
@@ -444,6 +444,117 @@ class DynBlocksTest(DNSDistTest):
         self.assertEquals(query, receivedQuery)
         self.assertEquals(response, receivedResponse)
 
+    def doTestRCodeRatio(self, name, rcode, noerrorcount, rcodecount):
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        response.answer.append(rrset)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(rcode)
+
+        # start with normal responses
+        for _ in range(noerrorcount-1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            receivedQuery.id = query.id
+            self.assertEquals(query, receivedQuery)
+            self.assertEquals(response, receivedResponse)
+
+        # wait for the maintenance function to run
+        time.sleep(2)
+
+        # we should NOT be dropped!
+        (_, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertEquals(receivedResponse, response)
+
+        # now with rcode!
+        sent = 0
+        allowed = 0
+        for _ in range(rcodecount):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, expectedResponse)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEquals(query, receivedQuery)
+                self.assertEquals(expectedResponse, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
+        self.assertGreaterEqual(allowed, rcodecount)
+
+        # wait for the maintenance function to run
+        time.sleep(2)
+
+        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, None)
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+        # again, over TCP this time
+        # start with normal responses
+        for _ in range(noerrorcount-1):
+            (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+            receivedQuery.id = query.id
+            self.assertEquals(query, receivedQuery)
+            self.assertEquals(response, receivedResponse)
+
+        # wait for the maintenance function to run
+        time.sleep(2)
+
+        # we should NOT be dropped!
+        (_, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertEquals(receivedResponse, response)
+
+        # now with rcode!
+        sent = 0
+        allowed = 0
+        for _ in range(rcodecount):
+            (receivedQuery, receivedResponse) = self.sendTCPQuery(query, expectedResponse)
+            sent = sent + 1
+            if receivedQuery:
+                receivedQuery.id = query.id
+                self.assertEquals(query, receivedQuery)
+                self.assertEquals(expectedResponse, receivedResponse)
+                allowed = allowed + 1
+            else:
+                # the query has not reached the responder,
+                # let's clear the response queue
+                self.clearToResponderQueue()
+
+        # we should have been able to send all our queries since the minimum number of queries is set to noerrorcount + rcodecount
+        self.assertGreaterEqual(allowed, rcodecount)
+
+        # wait for the maintenance function to run
+        time.sleep(2)
+
+        # we should now be dropped for up to self._dynBlockDuration + self._dynBlockPeriod
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, None)
+
+        # wait until we are not blocked anymore
+        time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+        # this one should succeed
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
 class TestDynBlockQPS(DynBlocksTest):
 
     _dynBlockQPS = 10
@@ -621,6 +732,8 @@ class TestDynBlockQPSActionTruncated(DNSDistTest):
         """
         name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
                                     60,
@@ -819,6 +932,29 @@ class TestDynBlockGroupServFails(DynBlocksTest):
         name = 'servfailrate.group.dynblocks.tests.powerdns.com.'
         self.doTestRCodeRate(name, dns.rcode.SERVFAIL)
 
+class TestDynBlockGroupServFailsRatio(DynBlocksTest):
+
+    _dynBlockPeriod = 2
+    _dynBlockDuration = 5
+    _config_params = ['_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+    _config_template = """
+    local dbr = dynBlockRulesGroup()
+    dbr:setRCodeRatio(DNSRCode.SERVFAIL, 0.2, %d, "Exceeded query rate", %d, 20)
+
+    function maintenance()
+           dbr:apply()
+    end
+
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testDynBlocksServFailRatio(self):
+        """
+        Dyn Blocks (group): Server Failure Ratio
+        """
+        name = 'servfailratio.group.dynblocks.tests.powerdns.com.'
+        self.doTestRCodeRatio(name, dns.rcode.SERVFAIL, 10, 10)
+
 class TestDynBlockResponseBytes(DynBlocksTest):
 
     _dynBlockBytesPerSecond = 200
index fdaeec32a0d368db5718b387d9bff6752bf2d559..42567f3cabf2935d28fb4baa5ab55f62c35af261 100644 (file)
@@ -33,6 +33,7 @@ class TestEDNSSelfGenerated(DNSDistTest):
         """
         name = 'no-edns.rcode.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -43,6 +44,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'no-edns.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.flags |= dns.flags.TC
 
@@ -83,6 +86,7 @@ class TestEDNSSelfGenerated(DNSDistTest):
         """
         name = 'edns-no-do.rcode.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -95,6 +99,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'edns-no-do.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.flags |= dns.flags.TC
 
@@ -141,6 +147,7 @@ class TestEDNSSelfGenerated(DNSDistTest):
         """
         name = 'edns-do.rcode.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=True)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -153,6 +160,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'edns-do.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=True)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.flags |= dns.flags.TC
 
@@ -200,6 +209,7 @@ class TestEDNSSelfGenerated(DNSDistTest):
         name = 'edns-options.rcode.edns-self.tests.powerdns.com.'
         ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24)
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512, want_dnssec=True)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -212,6 +222,8 @@ class TestEDNSSelfGenerated(DNSDistTest):
 
         name = 'edns-options.tc.edns-self.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512, want_dnssec=True)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query, our_payload=1042)
         expectedResponse.flags |= dns.flags.TC
 
@@ -284,6 +296,7 @@ class TestEDNSSelfGeneratedDisabled(DNSDistTest):
         """
         name = 'edns-no-do.rcode.edns-self-disabled.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -294,6 +307,8 @@ class TestEDNSSelfGeneratedDisabled(DNSDistTest):
 
         name = 'edns-no-do.tc.edns-self-disabled.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+        # dnsdist sets RA = RD for TC responses
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.flags |= dns.flags.TC
 
index 1abe575fd0991168e83c3ccbcc18d26311b05d9f..07741dba39d5a9237ae6174991d2fdd80cf4cf1c 100644 (file)
@@ -20,6 +20,7 @@ class TestRecordsCountOnlyOneAR(DNSDistTest):
         """
         name = 'refuseemptyar.recordscount.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -62,6 +63,7 @@ class TestRecordsCountOnlyOneAR(DNSDistTest):
         """
         name = 'refusetwoar.recordscount.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        query.flags &= ~dns.flags.RD
         query.additional.append(dns.rrset.from_text(name,
                                                     3600,
                                                     dns.rdataclass.IN,
@@ -92,6 +94,7 @@ class TestRecordsCountMoreThanOneLessThanFour(DNSDistTest):
         """
         name = 'refusenoan.recordscount.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
@@ -136,6 +139,7 @@ class TestRecordsCountMoreThanOneLessThanFour(DNSDistTest):
         """
         name = 'refusefouran.recordscount.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        query.flags &= ~dns.flags.RD
         rrset = dns.rrset.from_text_list(name,
                                          3600,
                                          dns.rdataclass.IN,
@@ -175,6 +179,7 @@ class TestRecordsCountNothingInNS(DNSDistTest):
                                     dns.rdatatype.NS,
                                     'ns.tests.powerdns.com.')
         query.authority.append(rrset)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
         expectedResponse.authority.append(rrset)
@@ -226,6 +231,7 @@ class TestRecordsCountNoOPTInAR(DNSDistTest):
         """
         name = 'refuseoptinar.recordscount.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
index 386fba655f91ce2d8235e620cae1ac5283233e40..70deba8f3c43e2d377165749baccf730a6a65802 100644 (file)
@@ -513,6 +513,10 @@ class TestRoutingBadWeightWRandom(DNSDistTest):
     s1 = newServer{address="127.0.0.1:%s", weight=-1}
     s2 = newServer{address="127.0.0.1:%s", weight=2147483648}
     """
+    _checkConfigExpectedOutput = b"""Error creating new server: downstream weight value must be greater than 0.
+Error creating new server: downstream weight value must be between 1 and 2147483647
+Configuration 'configs/dnsdist_TestRoutingBadWeightWRandom.conf' OK!
+"""
 
     def testBadWeightWRandom(self):
         """
index 4cedcd32bf59294dfdc00fea6839cc146d168946..2ae03bf8668a178a0b846bdd8c9d5ab3b6102567 100644 (file)
@@ -6,6 +6,10 @@ class TestSpoofingSpoof(DNSDistTest):
 
     _config_template = """
     addAction(makeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1"))
+    addAction(makeRule("spoofaction-aa.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {aa=true}))
+    addAction(makeRule("spoofaction-ad.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ad=true}))
+    addAction(makeRule("spoofaction-ra.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ra=true}))
+    addAction(makeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ra=false}))
     addAction(makeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com."))
     addAction("multispoof.spoofing.tests.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"}))
     newServer{address="127.0.0.1:%s"}
@@ -169,6 +173,96 @@ class TestSpoofingSpoof(DNSDistTest):
             self.assertTrue(receivedResponse)
             self.assertEquals(expectedResponse, receivedResponse)
 
+    def testSpoofActionSetAA(self):
+        """
+        Spoofing: Spoof via Action, setting AA=1
+        """
+        name = 'spoofaction-aa.spoofing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.AA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '2001:DB8::1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+
+    def testSpoofActionSetAD(self):
+        """
+        Spoofing: Spoof via Action, setting AD=1
+        """
+        name = 'spoofaction-ad.spoofing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.AD
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '2001:DB8::1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+
+    def testSpoofActionSetRA(self):
+        """
+        Spoofing: Spoof via Action, setting RA=1
+        """
+        name = 'spoofaction-ra.spoofing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        # dnsdist set RA = RD for spoofed responses
+        query.flags &= ~dns.flags.RD
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags |= dns.flags.RA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '2001:DB8::1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+
+    def testSpoofActionSetNoRA(self):
+        """
+        Spoofing: Spoof via Action, setting RA=0
+        """
+        name = 'spoofaction-nora.spoofing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.flags &= ~dns.flags.RA
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.AAAA,
+                                    '2001:DB8::1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.assertTrue(receivedResponse)
+            self.assertEquals(expectedResponse, receivedResponse)
+
 class TestSpoofingLuaSpoof(DNSDistTest):
 
     _config_template = """
index f975c9f403a03d30c6d1481ba207c8d857b86567..c0c8ebb675882aadf4dc1636092ab76f484344fc 100644 (file)
@@ -45,6 +45,7 @@ class TestTCPKeepAlive(DNSDistTest):
         """
         name = 'refused.tcpka.tests.powerdns.com.'
         query = dns.message.make_query(name, 'A', 'IN')
+        query.flags &= ~dns.flags.RD
         expectedResponse = dns.message.make_response(query)
         expectedResponse.set_rcode(dns.rcode.REFUSED)
 
index beedf56db07e0ea1ae4c51b8e01d5f910a3fa60d..e2760dc1dee19bd33985618775404f5de149ac8e 100644 (file)
@@ -643,3 +643,59 @@ tc.example.zone.rpz. 60 IN CNAME rpz-tcp-only.
         # check non-local policies, they should be overridden by the default policy
         self.checkNXD('tc.example.', 'A')
         self.checkNotBlocked('drop.example.')
+
+class RPZOrderingPrecedenceRecursorTesT(RPZRecursorTest):
+    """
+    This test makes sure that the recursor respects the RPZ ordering precedence rules
+    """
+
+    _confdir = 'RPZOrderingPrecedence'
+    _wsPort = 8042
+    _wsTimeout = 2
+    _wsPassword = 'secretpassword'
+    _apiKey = 'secretapikey'
+    _lua_config_file = """
+    rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
+    rpzFile('configs/%s/zone2.rpz', { policyName="zone2.rpz."})
+    """ % (_confdir, _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}
+sub.test 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}
+*.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
+""".format(soa=cls._SOA))
+
+        rpzFilePath = os.path.join(confdir, 'zone2.rpz')
+        with open(rpzFilePath, 'w') as rpzZone:
+            rpzZone.write("""$ORIGIN zone2.rpz.
+@ 3600 IN SOA {soa}
+sub.test.example.com.zone2.rpz. 60 IN CNAME .
+32.42.2.0.192.rpz-ip 60 IN CNAME .
+""".format(soa=cls._SOA))
+
+        super(RPZOrderingPrecedenceRecursorTesT, cls).generateRecursorConfig(confdir)
+
+    def testRPZ(self):
+        # we should first match on the qname (the wildcard, not on the exact name since
+        # we respect the order of the RPZ zones), see the pass-thru rule
+        # and stop RPZ processing. The subsequent rule on the content of the A
+        # should therefore not trigger a NXDOMAIN.
+        self.checkNotBlocked('sub.test.example.')