]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Merge pull request #8110 from zeha/urls-https
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Tue, 6 Aug 2019 09:17:01 +0000 (11:17 +0200)
committerGitHub <noreply@github.com>
Tue, 6 Aug 2019 09:17:01 +0000 (11:17 +0200)
Update URLs to use https scheme

36 files changed:
README.md
docs/backends/geoip.rst
docs/changelog/4.0.rst
docs/changelog/4.1.rst
docs/performance.rst
docs/secpoll.zone
docs/security-advisories/powerdns-advisory-2019-06.rst [new file with mode: 0644]
docs/upgrading.rst
ext/ipcrypt/Makefile.am
modules/gpgsqlbackend/4.1.0_to_4.2.0_schema.pgsql.sql
modules/gpgsqlbackend/schema.pgsql.sql
modules/pipebackend/coprocess.cc
modules/remotebackend/pipeconnector.cc
pdns/common_startup.cc
pdns/dnsdist-console.cc
pdns/dnsdist-ecs.cc
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua.cc
pdns/dnsdist-tcp.cc
pdns/dnsdist.cc
pdns/dnsdist.hh
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/docs/rules-actions.rst
pdns/dnsdistdist/doh.cc
pdns/doh.hh
pdns/lua-base4.cc
pdns/pdnsutil.cc
pdns/recursordist/docs/lua-scripting/hooks.rst
pdns/recursordist/docs/settings.rst
pdns/tcpreceiver.cc
pdns/tcpreceiver.hh
pdns/test-dnsdist_cc.cc
regression-tests.dnsdist/test_Advanced.py
regression-tests.dnsdist/test_BrokenAnswer.py
regression-tests.dnsdist/test_DOH.py
regression-tests.nobackend/counters/expected_result

index 0aa48750bdd5008d8b71f89e84e5a45fbeae463d..b0feeeba7b0712c539d9072cd09b5659a15baf26 100644 (file)
--- a/README.md
+++ b/README.md
@@ -180,7 +180,7 @@ The HTML documentation will now be available in `html-docs`.
 
 Solaris Notes
 -------------
-Use a recent gcc. OpenCSW is a good source, as is Solaris 11 IPS.
+Use a recent gcc (and other build tools), possibly from Solaris 11 IPS.
 
 If you encounter problems with the Solaris make, gmake is advised.
 
index 4b3811d67ef9383cd809d1f13d4d728ddd8e079e..402809a89bd64ecf9ced2c79aa4de0ec8ff61c8c 100644 (file)
@@ -11,7 +11,7 @@ GeoIP backend
 * Module name: geoip
 * Launch name: ``geoip``
 
-This backend allows visitors to be sent to a server closer to them, with
+This backend (which is a.k.a. the YAML backend) allows visitors to be sent to a server closer to them, with
 no appreciable delay, as would otherwise be incurred with a protocol
 level redirect. Additionally, the Geo Backend can be used to provide
 service over several clusters, any of which can be taken out of use
@@ -25,13 +25,13 @@ Prerequisites
 
 To compile the backend, you need libyaml-cpp 0.5 or later and libgeoip.
 
-You must have geoip database available. As of writing, on debian/ubuntu
-systems, you can use apt-get install geoip-database to get one, and the
+You must have a geoip database available. As of this writing, on debian/ubuntu
+systems, you can use ``apt-get install geoip-database`` to get one, and the
 backend is configured to use the location where these files are
 installed as source. On other systems you might need to alter the
-database-file and database-file6 attribute. If you don't need ipv4 or
+``database-file`` and ``database-file6`` attribute. If you don't need ipv4 or
 ipv6 support, set the respective setting to "". Leaving it unset leaves
-it pointing to default location, preventing the software from starting
+it pointing to default location, preventing the software from starting
 up.
 
 Since v4.2.0 libgeoip is optional. You can use also libmaxminddb, but
@@ -42,8 +42,8 @@ Configuration Parameters
 ------------------------
 
 These are the configuration file parameters that are available for the
-GeoIP backend. geoip-zones-files is the only thing you must set, if the
-defaults suite you.
+GeoIP backend. ``geoip-zones-files`` is the only thing you must set, if the
+defaults suit you.
 
 .. _setting-geoip-database-files:
 
@@ -62,7 +62,7 @@ to generate your own.
 
 For MMDB files, see `MaxMind's getting started guide <https://github.com/maxmind/getting-started-with-mmdb>`__.
 
-Since v4.2.0, database type is determined by file suffix, or you can use new syntax.
+Since v4.2.0, database type is determined by file suffix, or you can use the new syntax.
 New syntax is ``[driver:]path[;options]``.
 
 Drivers and options
@@ -109,7 +109,7 @@ flags and active/disabled state encoded in the key filenames.
 Zonefile format
 ---------------
 
-Zone configuration file uses YAML syntax. Here is simple example. Note
+Zone configuration files use YAML syntax. Here is simple example. Note
 that the ``‐`` before certain keys is part of the syntax.
 
 .. code-block:: yaml
index 06cc825076def546b46f8f29f49690bb1ceff6cd..77113f43bc3f5c0e865a027ba9cd7640e2f810f6 100644 (file)
@@ -1,6 +1,15 @@
 Changelogs for 4.0.x
 ====================
 
+PowerDNS Authoritative Server 4.0.9
+-----------------------------------
+
+Released 1st of August 2019
+
+This release contains the updated PostgreSQL schema for PowerDNS Security Advisory :doc:`2019-06 <../security-advisories/powerdns-advisory-2019-06>` (CVE-2019-10203).
+
+Upgrading is not enough - you need to manually apply the schema change: ``ALTER TABLE domains ALTER notified_serial TYPE bigint USING CASE WHEN notified_serial >= 0 THEN notified_serial::bigint END;``
+
 PowerDNS Authoritative Server 4.0.8
 -----------------------------------
 
index 23ffe94a642a2786aa97608adb08c9995f2d00c6..ece6654efe8c7ee9da7158f5e89d9ef864c855b0 100644 (file)
@@ -1,6 +1,20 @@
 Changelogs for 4.1.x
 ====================
 
+.. changelog::
+  :version: 4.1.11
+  :released: August 1st 2019
+
+  This release contains the updated PostgreSQL schema for PowerDNS Security Advisory :doc:`2019-06 <../security-advisories/powerdns-advisory-2019-06>` (CVE-2019-10203).
+
+  Upgrading is not enough - you need to manually apply the schema change: ``ALTER TABLE domains ALTER notified_serial TYPE bigint USING CASE WHEN notified_serial >= 0 THEN notified_serial::bigint END;``
+
+  .. change::
+    :tags: Bug Fixes
+    :pullreq: 8144
+
+    Update PostgreSQL schema for 2019-06.
+
 .. changelog::
   :version: 4.1.10
   :released: June 21st 2019
index e31ac892f7b6df046a39bb5af8cef86824af2476..850e352b9e285d8c0a62fb542501f5e64dcaaf58 100644 (file)
@@ -212,6 +212,12 @@ meta-cache-size
 ^^^^^^^^^^^^^^^
 Number of entries in the metadata cache
 
+.. _stat-open-tcp-connections:
+
+open-tcp-connections
+~~~~~~~~~~~~~~~~~~~~
+Number of currently open TCP connections
+
 .. _stat-overload-drops:
 
 overload-drops
index b44fab69e376a0345f0b8cd0f42298792daa5765..03919a0d83c0f03456c4e9781873686228ce4c13 100644 (file)
@@ -1,4 +1,4 @@
-@       86400   IN  SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2019071601 10800 3600 604800 10800
+@       86400   IN  SOA pdns-public-ns1.powerdns.com. pieter\.lexis.powerdns.com. 2019080101 10800 3600 604800 10800
 @       3600    IN  NS  pdns-public-ns1.powerdns.com.
 @       3600    IN  NS  pdns-public-ns2.powerdns.com.
 @       3600    IN  NS  tmpdns.powerdns.com.
@@ -35,6 +35,7 @@ auth-4.0.5.security-status                              60 IN TXT "3 Upgrade now
 auth-4.0.6.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-03.html"
 auth-4.0.7.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2018-03.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2018-05.html"
 auth-4.0.8.security-status                              60 IN TXT "1 OK"
+auth-4.0.9.security-status                              60 IN TXT "1 OK"
 auth-4.1.0-rc1.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.1.0-rc2.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
 auth-4.1.0-rc3.security-status                          60 IN TXT "3 Unsupported pre-release (known vulnerabilities)"
@@ -49,6 +50,7 @@ auth-4.1.7.security-status                              60 IN TXT "3 Upgrade now
 auth-4.1.8.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-04.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-05.html"
 auth-4.1.9.security-status                              60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-04.html https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-05.html"
 auth-4.1.10.security-status                             60 IN TXT "1 OK"
+auth-4.1.11.security-status                             60 IN TXT "1 OK"
 auth-4.2.0-alpha1.security-status                       60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-03.html"
 auth-4.2.0-beta1.security-status                        60 IN TXT "3 Upgrade now, see https://doc.powerdns.com/authoritative/security-advisories/powerdns-advisory-2019-03.html"
 auth-4.2.0-rc1.security-status                          60 IN TXT "1 OK"
diff --git a/docs/security-advisories/powerdns-advisory-2019-06.rst b/docs/security-advisories/powerdns-advisory-2019-06.rst
new file mode 100644 (file)
index 0000000..7eb8673
--- /dev/null
@@ -0,0 +1,34 @@
+PowerDNS Security Advisory 2019-06: Denial of service via crafted zone records
+==============================================================================
+
+-  CVE: CVE-2019-10203
+-  Date: July 30th, 2019
+-  Affects: PowerDNS Authoritative 4.0.0 and up, when using the gpgsql (PostgreSQL) backend
+-  Not affected: 4.2.0, 4.1.11, 4.0.9
+-  Severity: Low
+-  Impact: Denial of Service
+-  Exploit: This problem can be triggered via crafted records
+-  Risk of system compromise: No
+-  Solution: Update the database schema
+-  Workaround: run the process inside the guardian or inside a supervisor
+
+An issue has been found in PowerDNS Authoritative Server allowing an
+authorized user to cause the server to exit by inserting a crafted record in a
+MASTER type zone under their control. The issue is due to the fact that the
+Authoritative Server will exit when it tries to store the notified serial in
+the PostgreSQL database, if this serial cannot be represented in 31 bits.
+
+This issue has been assigned CVE-2019-10203.
+
+PowerDNS Authoritative up to and including 4.1.10 is affected. Please note
+that at the time of writing, PowerDNS Authoritative 3.4 and below are no
+longer supported, as described in
+https://doc.powerdns.com/authoritative/appendices/EOL.html.
+
+To fix the issue, run the following command against your PostgreSQL pdns
+database: `ALTER TABLE domains ALTER notified_serial TYPE bigint USING CASE
+WHEN notified_serial >= 0 THEN notified_serial::bigint END;`. No software
+changes are required.
+
+We would like to thank Klaus Darilion for finding and subsequently reporting
+this issue!
index 622d1087d14358e7738bad550550a8b7386c8c5e..19d1f8aa7b0c2c360c696d501f38c82110e55f82 100644 (file)
@@ -13,6 +13,8 @@ upgrade notes if your version is older than 3.4.2.
 
 - Superslave operation is no longer enabled by default, use :ref:`setting-superslave` to enable. This setting was called ``supermaster`` in some 4.2.0 prereleases.
 - The gsqlite3 backend, and the DNSSEC database for the BIND backend, have a new journal-mode setting. This setting defaults to `WAL <https://www.sqlite.org/wal.html>`_; older versions of PowerDNS did not set the journal mode, which means they used the SQLite default of DELETE.
+- Autoserial support has been removed. The ``change_date`` column has been removed from the ``domains`` table in all gsql backends, but leaving it in is harmless.
+- The :doc:`Generic PostgreSQL backend <backends/generic-postgresql>` schema has changed: the ``notified_serial`` column type in the ``domains`` table has been changed from ``INT DEFAULT NULL`` to ``BIGINT DEFAULT NULL``: ``ALTER TABLE domains ALTER notified_serial TYPE bigint USING CASE WHEN notified_serial >= 0 THEN notified_serial::bigint END;``
 
 4.1.0 to 4.1.1
 --------------
index a68b2d453cde047c9e3461bc550f9b53a5468570..cd66bfcd1786f7d380d72eb2353d561fdbe76794 100644 (file)
@@ -1,3 +1,6 @@
+EXTRA_DIST = \
+       LICENSE
+
 noinst_LTLIBRARIES = libipcrypt.la
 
 libipcrypt_la_SOURCES = \
index f91046250fdc2413a843b6af2ad90f11b66a8410..2333cf453599c15037ab59d72af56a68ef43b27a 100644 (file)
@@ -1 +1,2 @@
 ALTER TABLE records DROP COLUMN change_date;
+ALTER TABLE domains ALTER notified_serial TYPE bigint USING CASE WHEN notified_serial >= 0 THEN notified_serial::bigint END;
index e6c6b7c3bc9fc5809c68bb03f8caaf368bb0a353..911dd373ec5ae34ce1691448a47589300b83b752 100644 (file)
@@ -4,7 +4,7 @@ CREATE TABLE domains (
   master                VARCHAR(128) DEFAULT NULL,
   last_check            INT DEFAULT NULL,
   type                  VARCHAR(6) NOT NULL,
-  notified_serial       INT DEFAULT NULL,
+  notified_serial       BIGINT DEFAULT NULL,
   account               VARCHAR(40) DEFAULT NULL,
   CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
 );
index f3967301d07c35e06ae9a23f85f63b808fdc2dd0..db0ed5c1833ce2a6d05d5baafe4beef844a6a04f 100644 (file)
@@ -78,7 +78,7 @@ void CoProcess::launch(const char **argv, int timeout, int infd, int outfd)
     if(!(d_fp=fdopen(d_fd2[0],"r")))
       throw PDNSException("Unable to associate a file pointer with pipe: "+stringerror());
     if( d_timeout)
-      setbuf(d_fp,0); // no buffering please, confuses select
+      setbuf(d_fp,0); // no buffering please, confuses poll
   }
   else if(!d_pid) { // child
     signal(SIGCHLD, SIG_DFL); // silence a warning from perl
@@ -165,14 +165,7 @@ void CoProcess::receive(string &receive)
   receive.clear();
     
   if(d_timeout) {
-    struct timeval tv;
-    tv.tv_sec=d_timeout/1000;
-    tv.tv_usec=(d_timeout % 1000) * 1000;
-
-    fd_set rds;
-    FD_ZERO(&rds);
-    FD_SET(fileno(d_fp),&rds);
-    int ret=select(fileno(d_fp)+1,&rds,0,0,&tv);
+    int ret = waitForData(fileno(d_fp), 0, d_timeout * 1000);
     if(ret<0)
       throw PDNSException("Error waiting on data from coprocess: "+stringerror());
     if(!ret)
index a919c553de8cae24649f25d04918fbdb40542452..d30ed06e6399492d1f038127521b37dab00babae 100644 (file)
@@ -87,7 +87,7 @@ void PipeConnector::launch() {
     if(!(d_fp=std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(d_fd2[0],"r"), fclose)))
       throw PDNSException("Unable to associate a file pointer with pipe: "+stringerror());
     if (d_timeout)
-      setbuf(d_fp.get(),0); // no buffering please, confuses select
+      setbuf(d_fp.get(),0); // no buffering please, confuses poll
   }
   else if(!d_pid) { // child
     signal(SIGCHLD, SIG_DFL); // silence a warning from perl
@@ -157,13 +157,7 @@ int PipeConnector::recv_message(Json& output)
    while(1) {
      receive.clear();
      if(d_timeout) {
-       struct timeval tv;
-       tv.tv_sec = d_timeout/1000;
-       tv.tv_usec = (d_timeout % 1000) * 1000;
-       fd_set rds;
-       FD_ZERO(&rds);
-       FD_SET(fileno(d_fp.get()),&rds);
-       int ret=select(fileno(d_fp.get())+1,&rds,0,0,&tv);
+       int ret=waitForData(fileno(d_fp.get()), 0, d_timeout * 1000);
        if(ret<0) 
          throw PDNSException("Error waiting on data from coprocess: "+stringerror());
        if(!ret)
index 63a0d094f52028fdc3a7d90f489b15313cb9eb76..fa56dd687070de9adf5968d30b96acc371628e6c 100644 (file)
@@ -247,6 +247,11 @@ static uint64_t getSysUserTimeMsec(const std::string& str)
 
 }
 
+static uint64_t getTCPConnectionCount(const std::string& str)
+{
+  return TN->numTCPConnections();
+}
+
 static uint64_t getQCount(const std::string& str)
 try
 {
@@ -306,7 +311,8 @@ void declareStats(void)
   
   S.declare("tcp6-queries","Number of IPv6 TCP queries received");
   S.declare("tcp6-answers","Number of IPv6 answers sent out over TCP");
-    
+
+  S.declare("open-tcp-connections","Number of currently open TCP connections", getTCPConnectionCount);;
 
   S.declare("qsize-q","Number of questions waiting for database attention", getQCount);
 
index 75b3561afaa7ac99cab88ee87d9b514fee203967..e0f0db3693cf8fc40dc4ac060aaa7dde9d1a05d3 100644 (file)
@@ -299,7 +299,7 @@ void doConsole()
           }
         }
         else 
-          cout << g_outputBuffer;
+          cout << g_outputBuffer << std::flush;
         if(!getLuaNoSideEffect())
           feedConfigDelta(line);
       }
@@ -364,6 +364,7 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "clearDynBlocks", true, "", "clear all dynamic blocks" },
   { "clearQueryCounters", true, "", "clears the query counter buffer" },
   { "clearRules", true, "", "remove all current rules" },
+  { "ContinueAction", true, "action", "execute the specified action and continue the processing of the remaining rules, regardless of the return of the action" },
   { "DelayAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
   { "DelayResponseAction", true, "milliseconds", "delay the response by the specified amount of milliseconds (UDP-only)" },
   { "delta", true, "", "shows all commands entered that changed the configuration" },
index 5e8974d6983c9d9fdcd47aac8c78ee1b1273cba5..67fc5733728184d442c8793e3a91e35a5da3ced2 100644 (file)
@@ -492,7 +492,7 @@ bool isEDNSOptionInOpt(const std::string& packet, const size_t optStart, const s
     return false;
   }
   size_t p = optStart + 9;
-  uint16_t rdLen = (0x100*packet.at(p) + packet.at(p+1));
+  uint16_t rdLen = (0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1)));
   p += sizeof(rdLen);
   if (rdLen > (optLen - optRecordMinimumSize)) {
     return false;
@@ -500,9 +500,9 @@ bool isEDNSOptionInOpt(const std::string& packet, const size_t optStart, const s
 
   size_t rdEnd = p + rdLen;
   while ((p + 4) <= rdEnd) {
-    const uint16_t optionCode = 0x100*packet.at(p) + packet.at(p+1);
+    const uint16_t optionCode = 0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1));
     p += sizeof(optionCode);
-    const uint16_t optionLen = 0x100*packet.at(p) + packet.at(p+1);
+    const uint16_t optionLen = 0x100*static_cast<unsigned char>(packet.at(p)) + static_cast<unsigned char>(packet.at(p+1));
     p += sizeof(optionLen);
 
     if ((p + optionLen) > rdEnd) {
index 2943ad31899885cd2379db0a4037fc32f3568c79..7aabdf5f64612effe3e28a588a383a823df8c0c0 100644 (file)
@@ -1041,6 +1041,41 @@ private:
   std::string d_value;
 };
 
+class ContinueAction : public DNSAction
+{
+public:
+  ContinueAction(std::shared_ptr<DNSAction>& action): d_action(action)
+  {
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  {
+    if (d_action) {
+      /* call the action */
+      auto action = (*d_action)(dq, ruleresult);
+      bool drop = false;
+      /* apply the changes if needed (pool selection, flags, etc */
+      processRulesResult(action, *dq, *ruleresult, drop);
+    }
+
+    /* but ignore the resulting action no matter what */
+    return Action::None;
+  }
+
+  std::string toString() const override
+  {
+    if (d_action) {
+      return "continue after: " + (d_action ? d_action->toString() : "");
+    }
+    else {
+      return "no op";
+    }
+  }
+
+private:
+  std::shared_ptr<DNSAction> d_action;
+};
+
 template<typename T, typename ActionT>
 static void addAction(GlobalStateHolder<vector<T> > *someRulActions, luadnsrule_t var, std::shared_ptr<ActionT> action, boost::optional<luaruleparams_t> params) {
   setLuaSideEffect();
@@ -1340,4 +1375,8 @@ void setupLuaActions()
   g_lua.writeFunction("TagResponseAction", [](std::string tag, std::string value) {
       return std::shared_ptr<DNSResponseAction>(new TagResponseAction(tag, value));
     });
+
+  g_lua.writeFunction("ContinueAction", [](std::shared_ptr<DNSAction> action) {
+      return std::shared_ptr<DNSAction>(new ContinueAction(action));
+    });
 }
index fabbe51327526dab7de81a20ea20101cb4b63d5e..6a0e5211dea19baa2272cbb7a39be014d1b5d950 100644 (file)
@@ -87,7 +87,7 @@ void resetLuaSideEffect()
   g_noLuaSideEffect = boost::logic::indeterminate;
 }
 
-typedef std::unordered_map<std::string, boost::variant<bool, int, std::string, std::vector<std::pair<int,int> > > > localbind_t;
+typedef std::unordered_map<std::string, boost::variant<bool, int, std::string, std::vector<std::pair<int,int> >, std::map<std::string,std::string> > > localbind_t;
 
 static void parseLocalBindVars(boost::optional<localbind_t> vars, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus)
 {
@@ -1726,6 +1726,12 @@ void setupLuaConfig(bool client)
       if (vars->count("serverTokens")) {
         frontend->d_serverTokens = boost::get<const string>((*vars)["serverTokens"]);
       }
+      if (vars->count("customResponseHeaders")) {
+        for (auto const& headerMap : boost::get<std::map<std::string,std::string>>((*vars)["customResponseHeaders"])) {
+          std::pair<std::string,std::string> headerResponse = std::make_pair(headerMap.first, headerMap.second);
+          frontend->d_customResponseHeaders.push_back(headerResponse);
+        }
+      }
     }
     g_dohlocals.push_back(frontend);
     auto cs = std::unique_ptr<ClientState>(new ClientState(frontend->d_local, true, reusePort, tcpFastOpenQueueSize, interface, cpus));
index bc87e06a045422600ba8487b17742b8f616a7fbe..b250004d3a275896484c5e9a1ffdb98a2ae8df8c 100644 (file)
@@ -704,7 +704,7 @@ static void sendResponse(std::shared_ptr<IncomingTCPConnectionState>& state, str
 
 static void handleResponse(std::shared_ptr<IncomingTCPConnectionState>& state, struct timeval& now)
 {
-  if (state->d_responseSize < sizeof(dnsheader)) {
+  if (state->d_responseSize < sizeof(dnsheader) || !state->d_ds) {
     return;
   }
 
@@ -936,7 +936,7 @@ static void handleDownstreamIO(std::shared_ptr<IncomingTCPConnectionState>& stat
         state->d_currentPos = 0;
         state->d_querySentTime = now;
         iostate = IOState::NeedRead;
-        if (!state->d_isXFR) {
+        if (!state->d_isXFR && !state->d_outstanding) {
           /* don't bother with the outstanding count for XFR queries */
           ++state->d_ds->outstanding;
           state->d_outstanding = true;
@@ -1012,9 +1012,13 @@ static void handleDownstreamIO(std::shared_ptr<IncomingTCPConnectionState>& stat
     if (state->d_downstreamConnection && state->d_downstreamConnection->isFresh()) {
       ++state->d_downstreamFailures;
     }
-    if (state->d_outstanding && state->d_ds != nullptr) {
-      --state->d_ds->outstanding;
+
+    if (state->d_outstanding) {
       state->d_outstanding = false;
+
+      if (state->d_ds != nullptr) {
+        --state->d_ds->outstanding;
+      }
     }
     /* remove this FD from the IO multiplexer */
     iostate = IOState::Done;
index bb35f6804722d855c9bee0af2b8a5d13b730fe22..168f75300bb8021802ab6faab51b06dfcc7375dc 100644 (file)
@@ -1036,7 +1036,71 @@ static void spoofResponseFromString(DNSQuestion& dq, const string& spoofContent)
   }
 }
 
-static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, string& poolname, const struct timespec& now)
+bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::string& ruleresult, bool& drop)
+{
+  switch(action) {
+  case DNSAction::Action::Allow:
+    return true;
+    break;
+  case DNSAction::Action::Drop:
+    ++g_stats.ruleDrop;
+    drop = true;
+    return true;
+    break;
+  case DNSAction::Action::Nxdomain:
+    dq.dh->rcode = RCode::NXDomain;
+    dq.dh->qr=true;
+    ++g_stats.ruleNXDomain;
+    return true;
+    break;
+  case DNSAction::Action::Refused:
+    dq.dh->rcode = RCode::Refused;
+    dq.dh->qr=true;
+    ++g_stats.ruleRefused;
+    return true;
+    break;
+  case DNSAction::Action::ServFail:
+    dq.dh->rcode = RCode::ServFail;
+    dq.dh->qr=true;
+    ++g_stats.ruleServFail;
+    return true;
+    break;
+  case DNSAction::Action::Spoof:
+    spoofResponseFromString(dq, ruleresult);
+    return true;
+    break;
+  case DNSAction::Action::Truncate:
+    dq.dh->tc = true;
+    dq.dh->qr = true;
+    return true;
+    break;
+  case DNSAction::Action::HeaderModify:
+    return true;
+    break;
+  case DNSAction::Action::Pool:
+    dq.poolname=ruleresult;
+    return true;
+    break;
+  case DNSAction::Action::NoRecurse:
+    dq.dh->rd = false;
+    return true;
+    break;
+    /* non-terminal actions follow */
+  case DNSAction::Action::Delay:
+    dq.delayMsec = static_cast<int>(pdns_stou(ruleresult)); // sorry
+    break;
+  case DNSAction::Action::None:
+    /* fall-through */
+  case DNSAction::Action::NoOp:
+    break;
+  }
+
+  /* false means that we don't stop the processing */
+  return false;
+}
+
+
+static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, const struct timespec& now)
 {
   g_rings.insertQuery(now, *dq.remote, *dq.qname, dq.qtype, dq.len, *dq.dh);
 
@@ -1171,69 +1235,21 @@ static bool applyRulesToQuery(LocalHolders& holders, DNSQuestion& dq, string& po
 
   DNSAction::Action action=DNSAction::Action::None;
   string ruleresult;
+  bool drop = false;
   for(const auto& lr : *holders.rulactions) {
     if(lr.d_rule->matches(&dq)) {
       lr.d_rule->d_matches++;
       action=(*lr.d_action)(&dq, &ruleresult);
-
-      switch(action) {
-      case DNSAction::Action::Allow:
-        return true;
-        break;
-      case DNSAction::Action::Drop:
-        ++g_stats.ruleDrop;
-        return false;
-        break;
-      case DNSAction::Action::Nxdomain:
-        dq.dh->rcode = RCode::NXDomain;
-        dq.dh->qr=true;
-        ++g_stats.ruleNXDomain;
-        return true;
-        break;
-      case DNSAction::Action::Refused:
-        dq.dh->rcode = RCode::Refused;
-        dq.dh->qr=true;
-        ++g_stats.ruleRefused;
-        return true;
-        break;
-      case DNSAction::Action::ServFail:
-        dq.dh->rcode = RCode::ServFail;
-        dq.dh->qr=true;
-        ++g_stats.ruleServFail;
-        return true;
-        break;
-      case DNSAction::Action::Spoof:
-        spoofResponseFromString(dq, ruleresult);
-        return true;
-        break;
-      case DNSAction::Action::Truncate:
-        dq.dh->tc = true;
-        dq.dh->qr = true;
-        return true;
-        break;
-      case DNSAction::Action::HeaderModify:
-        return true;
-        break;
-      case DNSAction::Action::Pool:
-        poolname=ruleresult;
-        return true;
-        break;
-        /* non-terminal actions follow */
-      case DNSAction::Action::Delay:
-        dq.delayMsec = static_cast<int>(pdns_stou(ruleresult)); // sorry
-        break;
-      case DNSAction::Action::None:
-        /* fall-through */
-      case DNSAction::Action::NoOp:
-        break;
-      case DNSAction::Action::NoRecurse:
-        dq.dh->rd = false;
-        return true;
+      if (processRulesResult(action, dq, ruleresult, drop)) {
         break;
       }
     }
   }
 
+  if (drop) {
+    return false;
+  }
+
   return true;
 }
 
@@ -1415,9 +1431,7 @@ ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders&
     struct timespec now;
     gettime(&now);
 
-    string poolname;
-
-    if (!applyRulesToQuery(holders, dq, poolname, now)) {
+    if (!applyRulesToQuery(holders, dq, now)) {
       return ProcessQueryResult::Drop;
     }
 
@@ -1432,7 +1446,7 @@ ProcessQueryResult processQuery(DNSQuestion& dq, ClientState& cs, LocalHolders&
       return ProcessQueryResult::SendAnswer;
     }
 
-    std::shared_ptr<ServerPool> serverPool = getPool(*holders.pools, poolname);
+    std::shared_ptr<ServerPool> serverPool = getPool(*holders.pools, dq.poolname);
     dq.packetCache = serverPool->packetCache;
     auto policy = *(holders.policy);
     if (serverPool->policy != nullptr) {
index fff2234e9f5c2903ea90d37c381df70b5bb9c310..78869a1fc291b95a8a9cc4d5aadac986ebbfc8e5 100644 (file)
@@ -76,6 +76,7 @@ struct DNSQuestion
   Netmask ecs;
   boost::optional<Netmask> subnet;
   std::string sni; /* Server Name Indication, if any (DoT or DoH) */
+  std::string poolname;
   const DNSName* qname{nullptr};
   const ComboAddress* local{nullptr};
   const ComboAddress* remote{nullptr};
@@ -1152,6 +1153,7 @@ void resetLuaSideEffect(); // reset to indeterminate state
 
 bool responseContentMatches(const char* response, const uint16_t responseLen, const DNSName& qname, const uint16_t qtype, const uint16_t qclass, const ComboAddress& remote, unsigned int& consumed);
 bool processResponse(char** response, uint16_t* responseLen, size_t* responseSize, LocalStateHolder<vector<DNSDistResponseRuleAction> >& localRespRulactions, DNSResponse& dr, size_t addRoom, std::vector<uint8_t>& rewrittenResponse, bool muted);
+bool processRulesResult(const DNSAction::Action& action, DNSQuestion& dq, std::string& ruleresult, bool& drop);
 
 bool checkQueryHeaders(const struct dnsheader* dh);
 
index 25a40862dc1aebbf69207dee54d285ef8d08cde6..87564cc19344367db8508298b1400c2677335765 100644 (file)
@@ -126,6 +126,7 @@ Listen Sockets
   * ``ciphers``: str - The TLS ciphers to use, in OpenSSL format. Ciphers for TLS 1.3 must be specified via ``ciphersTLS13``.
   * ``ciphersTLS13``: str - The TLS ciphers to use for TLS 1.3, in OpenSSL format.
   * ``serverTokens``: str - The content of the Server: HTTP header returned by dnsdist. The default is "h2o/dnsdist".
+  * ``customResponseHeaders={}``: table - Set custom HTTP header(s) returned by dnsdist.
 
 .. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])
 
index 022656c4405b4beefe93775fcb7a98df716354a3..a5879eb9651f967a4c46008cf91e9127089d343d 100644 (file)
@@ -852,6 +852,15 @@ The following actions exist.
 
   Let these packets go through.
 
+.. function:: ContinueAction(action)
+
+  .. versionadded:: 1.4.0
+
+  Execute the specified action and override its return with None, making it possible to continue the processing.
+  Subsequent rules are processed after this action.
+
+  :param int action: Any other action
+
 .. function:: DelayAction(milliseconds)
 
   Delay the response by the specified amount of milliseconds (UDP-only).
@@ -881,6 +890,7 @@ The following actions exist.
 
   Send the the current query to a remote logger as a :doc:`dnstap <reference/dnstap>` message.
   ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
+  Subsequent rules are processed after this action.
 
   :param string identity: Server identity to store in the dnstap message
   :param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
@@ -892,6 +902,7 @@ The following actions exist.
 
   Send the the current response to a remote logger as a :doc:`dnstap <reference/dnstap>` message.
   ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DnstapMessage`, that can be used to modify the message.
+  Subsequent rules are processed after this action.
 
   :param string identity: Server identity to store in the dnstap message
   :param logger: The :func:`FrameStreamLogger <newFrameStreamUnixLogger>` or :func:`RemoteLogger <newRemoteLogger>` object to write to
@@ -1014,7 +1025,8 @@ The following actions exist.
     ``ipEncryptKey`` optional key added to the options table.
 
   Send the content of this query to a remote logger via Protocol Buffer.
-  ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes
+  ``alterFunction`` is a callback, receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
+  Subsequent rules are processed after this action.
 
   :param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
   :param string alterFunction: Name of a function to modify the contents of the logs before sending
@@ -1034,9 +1046,10 @@ The following actions exist.
     ``ipEncryptKey`` optional key added to the options table.
 
   Send the content of this response to a remote logger via Protocol Buffer.
-  ``alterFunction`` is the same callback that receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes
+  ``alterFunction`` is the same callback that receiving a :class:`DNSQuestion` and a :class:`DNSDistProtoBufMessage`, that can be used to modify the Protocol Buffer content, for example for anonymization purposes.
   ``includeCNAME`` indicates whether CNAME records inside the response should be parsed and exported.
-  The default is to only exports A and AAAA records
+  The default is to only exports A and AAAA records.
+  Subsequent rules are processed after this action.
 
   :param string remoteLogger: The :func:`remoteLogger <newRemoteLogger>` object to write to
   :param string alterFunction: Name of a function to modify the contents of the logs before sending
@@ -1099,6 +1112,7 @@ The following actions exist.
   .. versionadded:: 1.3.0
 
   Associate a tag named ``name`` with a value of ``value`` to this query, that will be passed on to the response.
+  Subsequent rules are processed after this action.
 
   :param string name: The name of the tag to set
   :param string value: The value of the tag
@@ -1108,6 +1122,7 @@ The following actions exist.
   .. versionadded:: 1.3.0
 
   Associate a tag named ``name`` with a value of ``value`` to this response.
+  Subsequent rules are processed after this action.
 
   :param string name: The name of the tag to set
   :param string value: The value of the tag
index 55ed64059466ad3a0bba37506478d6e653074339..f932c49f373f855e59f63d8c5afc68497196df06 100644 (file)
@@ -445,6 +445,12 @@ try
     return 0;
   }
 
+  constexpr int overwrite_if_exists = 1;
+  constexpr int maybe_token = 1;
+  for (auto const& headerPair : dsc->df->d_customResponseHeaders) {
+    h2o_set_header_by_str(&req->pool, &req->res.headers, headerPair.first.c_str(), headerPair.first.size(), maybe_token, headerPair.second.c_str(), headerPair.second.size(), overwrite_if_exists);
+  }
+
   if(auto tlsversion = h2o_socket_get_ssl_protocol_version(sock)) {
     if(!strcmp(tlsversion, "TLSv1.0"))
       ++dsc->df->d_tls10queries;
index 4e1acb0b56d020da6fe9997111d93569a6cf9530..b7b9bfe89fdf063ca6d69d11a4ce4a1925c594b6 100644 (file)
@@ -10,6 +10,7 @@ struct DOHFrontend
   std::string d_ciphers;
   std::string d_ciphers13;
   std::string d_serverTokens{"h2o/dnsdist"};
+  std::vector<std::pair<std::string, std::string>> d_customResponseHeaders;
   ComboAddress d_local;
 
   uint32_t d_idleTimeout{30};             // HTTP idle timeout in seconds
index 39dbb391c3abdcad7f65e15dbd12bb6e32459f25..a91b624b842a816087c400428c9dd8d9c49703a4 100644 (file)
@@ -66,6 +66,7 @@ void BaseLua4::prepareContext() {
   d_lw->registerFunction("getRawLabels", &DNSName::getRawLabels);
   d_lw->registerFunction<unsigned int(DNSName::*)()>("countLabels", [](const DNSName& name) { return name.countLabels(); });
   d_lw->registerFunction<size_t(DNSName::*)()>("wireLength", [](const DNSName& name) { return name.wirelength(); });
+  d_lw->registerFunction<size_t(DNSName::*)()>("wirelength", [](const DNSName& name) { return name.wirelength(); });
   d_lw->registerFunction<bool(DNSName::*)(const std::string&)>("equal", [](const DNSName& lhs, const std::string& rhs) { return lhs==DNSName(rhs); });
   d_lw->registerEqFunction(&DNSName::operator==);
   d_lw->registerToStringFunction<string(DNSName::*)()>([](const DNSName&dn ) { return dn.toString(); });
index 8eb3f016dbd04b95d7a982c9fba8320042292a38..69adc32fad6e2c2ffcce371feb76e317755d3f1a 100644 (file)
@@ -1776,6 +1776,7 @@ void testSchema(DNSSECKeeper& dk, const DNSName& zone)
   cout<<"Note: test-schema will try to create the zone, but it will not remove it."<<endl;
   cout<<"Please clean up after this."<<endl;
   cout<<endl;
+  cout<<"If this test reports an error and aborts, please check your database schema."<<endl;
   cout<<"Constructing UeberBackend"<<endl;
   UeberBackend B("default");
   cout<<"Picking first backend - if this is not what you want, edit launch line!"<<endl;
@@ -1817,13 +1818,13 @@ void testSchema(DNSSECKeeper& dk, const DNSName& zone)
     if(db->get(rrthrowaway)) // should not touch rr but don't assume anything
     {
       cout<<"Expected one record, got multiple, aborting"<<endl;
-      return;
+      exit(EXIT_FAILURE);
     }
     int size=rrget.content.size();
     if(size != 302)
     {
       cout<<"Expected 302 bytes, got "<<size<<", aborting"<<endl;
-      return;
+      exit(EXIT_FAILURE);
     }
   }
   cout<<"[+] content field is over 255 bytes"<<endl;
@@ -1860,14 +1861,36 @@ void testSchema(DNSSECKeeper& dk, const DNSName& zone)
   if(before != DNSName("_underscore")+zone)
   {
     cout<<"before is wrong, got '"<<before.toString()<<"', expected '_underscore."<<zone.toString()<<"', aborting"<<endl;
-    return;
+    exit(EXIT_FAILURE);
   }
   if(after != zone)
   {
     cout<<"after is wrong, got '"<<after.toString()<<"', expected '"<<zone.toString()<<"', aborting"<<endl;
-    return;
+    exit(EXIT_FAILURE);
   }
   cout<<"[+] ordername sorting is correct for names starting with _"<<endl;
+  cout<<"Setting low notified serial"<<endl;
+  db->setNotified(di.id, 500);
+  db->getDomainInfo(zone, di);
+  if(di.notified_serial != 500) {
+    cout<<"[-] Set serial 500, got back "<<di.notified_serial<<", aborting"<<endl;
+    exit(EXIT_FAILURE);
+  }
+  cout<<"Setting serial that needs 32 bits"<<endl;
+  try {
+    db->setNotified(di.id, 2147484148);
+  } catch(const PDNSException &pe) {
+    cout<<"While setting serial, got error: "<<pe.reason<<endl;
+    cout<<"aborting"<<endl;
+    exit(EXIT_FAILURE);
+  }
+  db->getDomainInfo(zone, di);
+  if(di.notified_serial != 2147484148) {
+    cout<<"[-] Set serial 2147484148, got back "<<di.notified_serial<<", aborting"<<endl;
+    exit(EXIT_FAILURE);
+  } else {
+    cout<<"[+] Big serials work correctly"<<endl;
+  }
   cout<<endl;
   cout<<"End of tests, please remove "<<zone<<" from domains+records"<<endl;
 }
index 79ec8b0e5592ad57e5aab3e46026ef077432ae10..7a5781c198b564aaa9da5bb4d284883002dd4b4a 100644 (file)
@@ -323,8 +323,8 @@ For example, to send a custom SNMP trap containing the qname from the
 
 Maintenance callback
 --------------------
-Starting with version 4.1.4 of the recursor, it is possible to define a `maintenance()` callback function that will be called periodically.
-This function expects no argument and doesn't return any value
+Starting with version 4.2.0 of the recursor, it is possible to define a `maintenance()` callback function that will be called periodically.
+This function expects no argument and doesn't return any value.
 
 .. code-block:: Lua
 
index e76d0e7c2daaed990463e924d1d2a445239bea6a..d7c9f4ac176ed0ce32eeb3211e65d8f4aaa5a4ee 100644 (file)
@@ -818,7 +818,7 @@ Path to a lua file to manipulate the Recursor's answers. See :doc:`lua-scripting
 
 ``lua-maintenance-interval``
 ----------------------------
-.. versionadded:: 4.1.4
+.. versionadded:: 4.2.0
 
 -  Integer
 -  Default: 1
@@ -1427,7 +1427,7 @@ Use 0 to disable.
 .. versionadded:: 4.2.0
 
 -  String
--  Default: "cache-bytes, packetcache-bytes, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
+-  Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
 
 A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via the API for performance reasons.
 These statistics can still be retrieved individually by specifically asking for it.
@@ -1439,7 +1439,7 @@ These statistics can still be retrieved individually by specifically asking for
 .. versionadded:: 4.2.0
 
 -  String
--  Default: "cache-bytes, packetcache-bytes, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
+-  Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
 
 A list of comma-separated statistic names, that are prevented from being exported via carbon for performance reasons.
 
@@ -1450,7 +1450,7 @@ A list of comma-separated statistic names, that are prevented from being exporte
 .. versionadded:: 4.2.0
 
 -  String
--  Default: "cache-bytes, packetcache-bytes, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
+-  Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
 
 A list of comma-separated statistic names, that are disabled when retrieving the complete list of statistics via `rec_control get-all`, for performance reasons.
 These statistics can still be retrieved individually.
@@ -1472,7 +1472,7 @@ Can be read out using ``rec_control top-remotes``.
 .. versionadded:: 4.2.0
 
 -  String
--  Default: "cache-bytes, packetcache-bytes, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
+-  Default: "cache-bytes, packetcache-bytes, special-memory-usage, ecs-v4-response-bits-*, ecs-v6-response-bits-*"
 
 A list of comma-separated statistic names, that are prevented from being exported via SNMP, for performance reasons.
 
index 0ea41bb88f43ff97cb59fabcefdc54f8e16f676e..c925766966087e01eb4a7de600049bd86a509010 100644 (file)
@@ -68,6 +68,7 @@ extern StatBag S;
 
 pthread_mutex_t TCPNameserver::s_plock = PTHREAD_MUTEX_INITIALIZER;
 Semaphore *TCPNameserver::d_connectionroom_sem;
+unsigned int TCPNameserver::d_maxTCPConnections = 0;
 PacketHandler *TCPNameserver::s_P; 
 NetmaskGroup TCPNameserver::d_ng;
 size_t TCPNameserver::d_maxTransactionsPerConn;
@@ -1195,6 +1196,7 @@ TCPNameserver::TCPNameserver()
 
 //  sem_init(&d_connectionroom_sem,0,::arg().asNum("max-tcp-connections"));
   d_connectionroom_sem = new Semaphore( ::arg().asNum( "max-tcp-connections" ));
+  d_maxTCPConnections = ::arg().asNum( "max-tcp-connections" );
   d_tid=0;
   vector<string>locals;
   stringtok(locals,::arg()["local-address"]," ,");
@@ -1387,3 +1389,9 @@ void TCPNameserver::thread()
 }
 
 
+unsigned int TCPNameserver::numTCPConnections()
+{
+  int room;
+  d_connectionroom_sem->getValue( &room);
+  return d_maxTCPConnections - room;
+}
index cfd6d3001678f40ec58f04e1a32c22ec1d3c599b..fd8b33c22f8a7d9daec064a505dd97b423f2a5b4 100644 (file)
@@ -47,6 +47,7 @@ public:
   TCPNameserver();
   ~TCPNameserver();
   void go();
+  unsigned int numTCPConnections();
 private:
 
   static void sendPacket(std::shared_ptr<DNSPacket> p, int outsock);
@@ -65,6 +66,7 @@ private:
   static PacketHandler *s_P;
   pthread_t d_tid;
   static Semaphore *d_connectionroom_sem;
+  static unsigned int d_maxTCPConnections;
   static NetmaskGroup d_ng;
   static size_t d_maxTransactionsPerConn;
   static size_t d_maxConnectionsPerClient;
index 100dd47109fbf539896e8d769d4cd45b1a4610b2..b81b4ab36b4b83fb3126b8aaff23892fb59a313a 100644 (file)
@@ -1451,6 +1451,7 @@ BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt) {
   ecsOpts.source = Netmask(ComboAddress("127.0.0.1"), ECSSourcePrefixV4);
   const string ecsOptionStr = makeEDNSSubnetOptsString(ecsOpts);
   const size_t sizeOfECSContent = ecsOptionStr.size();
+  const size_t sizeOfECSOption = /* option code */ 2 + /* option length */ 2 + sizeOfECSContent;
   EDNSCookiesOpt cookiesOpt;
   cookiesOpt.client = string("deadbeef");
   cookiesOpt.server = string("deadbeef");
@@ -1563,6 +1564,28 @@ BOOST_AUTO_TEST_CASE(test_isEDNSOptionInOpt) {
     query.resize(query.size() - 1);
     BOOST_CHECK_THROW(locateEDNSOption(query, EDNSOptionCode::ECS, &optContentStart, &optContentLen), std::range_error);
   }
+
+  {
+    /* valid EDNS, one 65002 after an ECS */
+    vector<uint8_t> query;
+    DNSPacketWriter pw(query, qname, qtype, qclass, 0);
+    DNSPacketWriter::optvect_t opts;
+    opts.push_back(make_pair(EDNSOptionCode::ECS, ecsOptionStr));
+    opts.push_back(make_pair(65535, cookiesOptionStr));
+    pw.addOpt(512, 0, 0, opts);
+    pw.commit();
+
+    bool found = locateEDNSOption(query, 65535, &optContentStart, &optContentLen);
+    BOOST_CHECK_EQUAL(found, true);
+    if (found == true) {
+      BOOST_CHECK_EQUAL(optContentStart, optRDExpectedOffset + sizeof(uint16_t) /* RD len */ + sizeOfECSOption + /* option code */ 2 + /* option length */ 2);
+      BOOST_CHECK_EQUAL(optContentLen, cookiesOptionStr.size());
+    }
+
+    /* truncated packet */
+    query.resize(query.size() - 1);
+    BOOST_CHECK_THROW(locateEDNSOption(query, 65002, &optContentStart, &optContentLen), std::range_error);
+  }
 }
 
 BOOST_AUTO_TEST_SUITE_END();
index 4a4c7e55535a62170cc1429b3518eed36561fd46..bb309b76387b98ce4489863eb5ee6cd35f4ee685 100644 (file)
@@ -1773,3 +1773,54 @@ class TestSetRules(DNSDistTest):
             (_, receivedResponse) = sender(query, response=None, useQueue=False)
             self.assertTrue(receivedResponse)
             self.assertEquals(expectedResponse, receivedResponse)
+
+class TestAdvancedContinueAction(DNSDistTest):
+
+    _config_template = """
+    newServer{address="127.0.0.1:%s", pool="mypool"}
+    addAction("nocontinue.continue-action.advanced.tests.powerdns.com.", PoolAction("mypool"))
+    addAction("continue.continue-action.advanced.tests.powerdns.com.", ContinueAction(PoolAction("mypool")))
+    addAction(AllRule(), DisableValidationAction())
+    """
+
+    def testNoContinue(self):
+        """
+        Advanced: Query routed to pool, PoolAction should be terminal
+        """
+
+        name = 'nocontinue.continue-action.advanced.tests.powerdns.com.'
+
+        query = dns.message.make_query(name, 'A', 'IN')
+        expectedQuery = dns.message.make_query(name, 'A', 'IN')
+
+        response = dns.message.make_response(query)
+        expectedResponse = dns.message.make_response(query)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            self.assertEquals(receivedQuery, expectedQuery)
+            self.assertEquals(receivedResponse, expectedResponse)
+
+    def testNoContinue(self):
+        """
+        Advanced: Query routed to pool, ContinueAction() should not stop the processing
+        """
+
+        name = 'continue.continue-action.advanced.tests.powerdns.com.'
+
+        query = dns.message.make_query(name, 'A', 'IN')
+        expectedQuery = dns.message.make_query(name, 'A', 'IN')
+        expectedQuery.flags |= dns.flags.CD
+
+        response = dns.message.make_response(query)
+        expectedResponse = dns.message.make_response(query)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            expectedQuery.id = receivedQuery.id
+            self.assertEquals(receivedQuery, expectedQuery)
+            print(receivedResponse)
+            print(expectedResponse)
+            self.assertEquals(receivedResponse, expectedResponse)
index a706dfa7e42d9bb78a0cf0c82495f6f843ddb5b5..78bd713358beaa837625c1b54cbf667850e62378 100644 (file)
@@ -25,7 +25,7 @@ def responseCallback(request):
     raw = response.to_wire()
     # first label length of this rrset is at 12 (dnsheader) + length(qname) + 2 (leading label length + trailing 0) + 2 (qtype) + 2 (qclass)
     offset = 12 + len(str(request.question[0].name)) + 2 + 2 + 2
-    altered = raw[:offset] + chr(255).encode() + raw[offset+1:]
+    altered = raw[:offset] + b'\xff' + raw[offset+1:]
     return altered
 
 class TestBrokenAnswerECS(DNSDistTest):
index 4943c96ec11443eb2e2b6befc8ea0843af571c4f..8a09652bd2b42a81f0dafc71658796d8f0fa0772 100644 (file)
@@ -5,7 +5,7 @@ import clientsubnetoption
 from dnsdisttests import DNSDistTest
 
 import pycurl
-
+from io import BytesIO
 #from hyper import HTTP20Connection
 #from hyper.ssl_compat import SSLContext, PROTOCOL_TLSv1_2
 
@@ -33,12 +33,14 @@ class DNSDistDOHTest(DNSDistTest):
     def sendDOHQuery(cls, port, servername, baseurl, query, response=None, timeout=2.0, caFile=None, useQueue=True, rawQuery=False, customHeaders=[]):
         url = cls.getDOHGetURL(baseurl, query, rawQuery)
         conn = cls.openDOHConnection(port, caFile=caFile, timeout=timeout)
+        response_headers = BytesIO()
         #conn.setopt(pycurl.VERBOSE, True)
         conn.setopt(pycurl.URL, url)
         conn.setopt(pycurl.RESOLVE, ["%s:%d:127.0.0.1" % (servername, port)])
         conn.setopt(pycurl.SSL_VERIFYPEER, 1)
         conn.setopt(pycurl.SSL_VERIFYHOST, 2)
         conn.setopt(pycurl.HTTPHEADER, customHeaders)
+        conn.setopt(pycurl.HEADERFUNCTION, response_headers.write)
         if caFile:
             conn.setopt(pycurl.CAINFO, caFile)
 
@@ -47,6 +49,7 @@ class DNSDistDOHTest(DNSDistTest):
 
         receivedQuery = None
         message = None
+        cls._response_headers = ''
         data = conn.perform_rb()
         rcode = conn.getinfo(pycurl.RESPONSE_CODE)
         if rcode == 200:
@@ -55,6 +58,7 @@ class DNSDistDOHTest(DNSDistTest):
         if useQueue and not cls._fromResponderQueue.empty():
             receivedQuery = cls._fromResponderQueue.get(True, timeout)
 
+        cls._response_headers = response_headers.getvalue()
         return (receivedQuery, message)
 
     @classmethod
@@ -128,11 +132,13 @@ class TestDOH(DNSDistDOHTest):
     _serverName = 'tls.tests.dnsdist.org'
     _caCert = 'ca.pem'
     _dohServerPort = 8443
-    _serverName = 'tls.tests.dnsdist.org'
+    _customResponseHeader1 = 'access-control-allow-origin: *'
+    _customResponseHeader2 = 'user-agent: derp'
     _dohBaseURL = ("https://%s:%d/" % (_serverName, _dohServerPort))
     _config_template = """
     newServer{address="127.0.0.1:%s"}
-    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" })
+
+    addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, {customResponseHeaders={["access-control-allow-origin"]="*",["user-agent"]="derp"}})
 
     addAction("drop.doh.tests.powerdns.com.", DropAction())
     addAction("refused.doh.tests.powerdns.com.", RCodeAction(DNSRCode.REFUSED))
@@ -164,6 +170,8 @@ class TestDOH(DNSDistDOHTest):
         self.assertTrue(receivedResponse)
         receivedQuery.id = expectedQuery.id
         self.assertEquals(expectedQuery, receivedQuery)
+        self.assertTrue((self._customResponseHeader1) in self._response_headers.decode())
+        self.assertTrue((self._customResponseHeader2) in self._response_headers.decode())
         self.checkQueryEDNSWithoutECS(expectedQuery, receivedQuery)
         self.assertEquals(response, receivedResponse)
 
index cf1ba2fd109a868d63a44052523e70781163be4e..7e73f4fb6546cbe9509ae31312a5d45868019a30 100644 (file)
@@ -11,6 +11,7 @@ dnsupdate-refused=0
 incoming-notifications=0
 key-cache-size=0
 meta-cache-size=3
+open-tcp-connections=0
 overload-drops=0
 packetcache-size=4
 qsize-q=0